본문 바로가기

IT for developer/Java

Java Concurrency Part 3 – Thread Pools


쓰레드 풀은 쓰레드의 수, 쓰레드 재사용, 스케쥴링등를 수행할 수 있다.

흠 지금 보고 있는 사이트가 너무 심플하게 나와서 다른사이트를 보거나 API를 보고 정리해야할듯.

참고 사이트: http://www.baptiste-wicht.com/2010/09/java-concurrency-part-7-executors-and-thread-pools/

쓰레드 풀을 살펴보기전에 기반 클래스들을 살펴보자.

Executor 라는 클래스

보통 Thread를 실행하기 위해서 Runnable 인터페이스를 구현한 클래스를 생성하고 다음과 같이 쓰레드를 실행한다.

 new Thread(new(RunnableTask())). start() 

상당히 심플하지만 여러개의 쓰레드를 동시에 실행하고 끝날 때 까지 기다리고 하는등의 작업을 수행하려면 난감하다. 자바에서 이러한 문제를 해결하기 위한 해결책이 바로 Executor 이다.

Executor는 송신된 Runnable 태스크를 실행하는 데 사용된다.

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());

일반적으로 다음과 같은 유사한 동작을 수행할 것이다.

 class ThreadPerTaskExecutor implements Executor {
     public void execute(Runnable r) {
         new Thread(r). start();
     }
 }


ExecutorService

Executor 인터페이스를 상속받고 있는 인터페이스로써 종료를 관리하는 메소드, 태스크의 진행상황을 추적할 수 있는 기능을 제공하는 Executor 이다.

종료를 위해 shutdown(), shutdownNow() 함수를 제공한다. 
 isTerminated(): shutdown 중에 모든 태스크가 완료되었는지 리턴한다. 



ThreadPoolExecutor

ExecutorService를 구현한 클래스.
풀된 쓰레드중 하나를 사용해서 태스크를 실행하는 ExecutorService 이다.
 
쓰레드풀 타입은 다음과 같다.
1. 단일 쓰레드 Executor: 단지 하나의 쓰레드를 가지는 쓰레드풀 . 제출된 모든 쓰레드들은 순차적으로 실행된다. Executors.newSingleThreadExecutor()
2. 캐시된 쓰레드 풀: 동시에 태스크를 실행할 필요가 있는 개수만큼 쓰레드를 생성한다. 오래된 쓰레드들은 새로운 태스크를 위해 재사용된다. Exeucotors.newCachedThreadPool()
3. 고정 쓰레드 풀: 쓰레드의 개수가 고정이다. 태스크를 얻을 수 없는 쓰레드들은 대기상태로 있는다. Executors.newFixedThreadPool()
4. 스케쥴된 쓰레드 풀: 태스크를 스케쥴하기 위해 만드는 쓰레드 풀. Executors.newScheduledThreadPool()
5. 단일 쓰레드 스케쥴된 풀: 태스크를 스케쥴하기 위해 하나의 쓰레드를 가지고 있는 쓰레드풀 Executors.newSingleThreadScheduledExecutor()
 



일단 쓰레드풀을 생성하면 Task를 제출할 수 있다. (Runnable or Callable) . 태스크의 미래 상태를나태내는 Future 를 리턴한다. (태스크가 끝났으면 null을 리턴한다.)


예를 들어, 다음과 같은 Callable을 가지고 있고:
 
private final class StringTask implements Callable<String>{   
public String call(){ //Long operations   return "Run"; } }

4개의 쓰레드를 사용하여 태스크를 10번 수행하기 원한다면 다음과 같은 코드를 사용할 수 있다.

ExecutorService pool = Executors.newFixedThreadPool(4);
 
for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}

쓰레드 풀을 셧다운하기 원한다면 다음과 같은 코드를 사용한다.

pool.shutdown();


태스크의 결과를 가져오기.


ExecutorService pool = Executors.newFixedThreadPool(4);
 
List<Future<String>> futures = new ArrayList<Future<String>>(10);
 
for(int i = 0; i < 10; i++){
   futures.add(pool.submit(new StringTask()));
}
 
for(Future<String> future : futures){
   String result = future.get();
 
   //Compute the result
}
 
pool.shutdown();

그러나, 위에 코드는 뭔가 문제가 있다. 첫 번째 태스크의 계산이 오래걸리면 다른 캐스크 모두 첫번째 태스크가 끝나길 기다리게 된다. future.get()에서 대기되기 때문!


좀 더 깔끔한 API를 지원한다. 

ExecutorService threadPool = Executors.newFixedThreadPool(4);
CompletionService<String> pool = new ExecutorCompletionService<String>(threadPool);
 
for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}
 
for(int i = 0; i < 10; i++){
   String result = pool.take().get();
 
   //Compute the result
}
threadPool.shutdown();