Back-end/JAVA,Spring

[java] Thread Pool 쓰레드 풀이란 ?

cheersHena 2021. 4. 8. 22:56
반응형

프로세스 중 병렬 작업처리가 많아지면 스레드 개수가 증가되고 그에따른 스레드생성과 스케줄링으로 인해 CPU가 바빠져 메모리 사용량이 늘어난다. 따라서 시스템성능이 저하되고, 갑작스러운 병렬작업의 폭증에 따른 스레드 폭증을 막으려면 스레드 풀 Thread Pool을 사용해야 한다.

Thread Pool 스레드 풀 

스레드 풀은 작업처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업큐 (Queue)에 들어오는 작업들을 하나씩 스레드가 맡아 처리한다. 그렇게 하면 작업처리 요청이 폭증되어도 스레드의 전체개수가 늘어나지 않으므로(제한해서 하나씩 처리하기 때문)  시스템 성능이 급격히 저하되지 않는다.

 

스레드풀

 

자바는 스레드풀을 생성하고 사용할 수 있도록

java.util.concurrent.Executors 클래스와 java.util.concurrent.ExecutorService 인터페이스를 제공하고 있다.

이것을 이용하면 간단히 쓰레드풀을 생성하여 병렬처리를 할 수 있다.

 

ExecutorService 생성

Executors 는 ExecutorService를 생성하며 다음 매소드를 제공하여 쓰레드 풀 개수 및 종류를 정할 수 있다.

 

  • newFixed ThreadPool(int) : 인자 개수만큼 고정된 프레드풀을 만든다.
  • newCachedThredPool() : 필요할때 필요한 만큼 쓰레드풀 생성. 이미 생성된 쓰레드를 재활용 할수 있어 성능 용이.
  • newScheculedThreadPool(int) : 일정 시간 뒤에 실행되는 작업이나, 주기적으로 수행되는 작업이 있다면 스케줄스레드 풀을 활용 할 수 있다.
  • newSingleThredExecutor() : 쓰레드 1개인 ExecutorService를 리턴한다. 싱글 스레드에서 동작하는 작업 처리시 사용한다.

 

작업생성

 하나의 작업은 Runnable(리턴값 없음) 또는 Callable(리턴값 존재) 구현 클래스로 표현한다.

스레드 풀의 스레드는 작업 큐에서

Runnable 객체를 가져와 run() 메소드를 실행하거나, Callable 객체를 가져와 call()메소드를 실행한다. 

 

작업처리 요청

ExecutorService의 작업큐에 Runnable 또는 Callable객체를 넣는 행위를 말한다.

ExecutorService는 작업처리 요청을 위해 두가지 메소드를 제공한다.

 

리턴타입

리턴타입

메소드명(매개변수)

설명

void

execute(Runnable Command) 

Runnable 을 작업큐에 저장

작업처리결과(리턴값) 없음 

Future<?>
Future<V>
Future<V>

submit(Runnable task)

submit(Runnable task, V result)

submit(Callable<V> task>) 

Runnable 또는 Callable 작업큐에 저장
리턴된 Future을 통해 작업처리 결과(리턴값)을 얻을 수 있음. 

 

스레드 풀에게 작업을 시키기 전 작업을 생성시켜야 작업처리를 요청할 수 있다.

작업 생성은 Runnable 인터페이스 or Callable 인터페이스를 구현한 클래스로 작업요청할 코드를 삽입해 작업을 만들 수 있다. 둘의 차이점은 Runnable의 run() 메서드는 리턴값이 없고, Callable의 call() 메서드는 리턴 값이 있다. 

 

ExecutorService에 작업 Job 추가

Executors로 ExecutorService를 생성했다면, 서비스는 작업을 처리한다. 

ExecutorService.submit() 또는 ExecutorService.execute() 메소드로 작업을 추가한다. 

 

execute() :

- 작업 처리 결과를 반환하지 않는다.

- 작업처리 도중 예외 발생시 스레드가 종료되고 해당 스레드는 스레드풀에서 제거된다. 

- 스레드풀은 다른 작업처리를 위해 새로운 스레드를 생성한다.

 

submit():

- 작업 처리 결과를 반환한다.

- 작업처리 도중 예외가 발생하더라도 스레드는 종료되지 않고 다음 작업을 위해 재사용된다.

- 가급적 스레드의 생성 오버헤더를 줄이기 위해 submit()을 사용하는 것이 좋다. 

 

ExecutorService의 submit() 메소드는 매개값으로 준 Runnable 또는 Callable작업을 스레드 풀의 작업 큐에 저장하고 즉시 Future 객체를 리턴한다. Future객체는 작업결과가 아니라 작업이 완료될때까지 기다렸다가 (지연) 최종결과를 얻는데 사용된다. 그래서 Future을 지연완료 객체라도 한다.

 

리턴값이 있는(callable) 작업완료 통보

스레드풀의 스레드가 작업완료 후에 처리결과인 리턴값을 얻어야된다면 작업 객체를 Callable로 생성하면 된다.

다음은 Callable객체를 생성하는 코드다. 제네릭 타입 <> 파라미터 T는 call() 메소드가 리턴하는 타입이어야 한다. 

 

Future future = executorService.submit(taskThread);

Callable<T> taskThread = new Callable<T> () {

	@Override
    public T call() throws Exception{
    
    //스레드가 처리할 내용 
    
    return T; 
    
    }
}; 

 

Callable 작업의 처리요청은 ExecutorService의 submit()메소드를 호출한다. 

submit()메소드는 작업 큐에 Callable객체를 저장하고 즉시 Future<T>를 리턴한다.

 

 

리턴값이 없는(Runnable) 작업완료 통보

이 경우에는 작업 객체를 Runnble객체로 생성하면 된다. 

 

Future future = executorService.submit(taskThread);

Runnable taskThread = new Runnable() {

	@Override
    public void run(){
    
    //스레드가 처리할 내용 
    
    }
}; 

Runnable은 리턴값이 없음에도 불구하고 Future객체를 리턴하는데, 이것은 스레드가 작업처리를 정상적으로 완료했는지, 처리도중 예외가 발생했는지 확인하기 위해서다. 정상완료 시 Future의 get메소드는 null을 리턴한다.

작업도중 예외발생시 ExecutorException을 발생시킨다. 

 

작업완료 순으로 통보.

작업요청 순서대로 작업처리가 완료되는 것은 아니다.

작업의 양과 스레드 스케쥴링에 따라서 먼저 요청한 작업이 나중에 완료되기도 한다.

여러개의 작업들이 순차적으로 처리될 필요가 없고, 처리결과도 순차적으로 이용할 필요가 없다면 작업 처리가 완료된 순으로 결과를 얻어 이용한다.

 

스레드풀에서 작업처리가 완료된 것만 통보받는 방법은

CompletionService 의 처리완료된 작업을 가져오는 poll()과 take()메소드를 사용하면 된다.

 
반응형