커스텀 스레드 풀


ThreadPoolExecutor 설정


ThreadPoolExecutor에 의해 스레드의 생성과 종료뿐만 아니라 Task의 큐잉에도 사용되며 다음과 같은 생성자에서 수행된다.



 ThreadPoolExecutor executor = new ThreadPoolExecutor(

   int corePoolSize, //핵심풀크기

   int maximumPoolSie,  //최대풀크기

   long keepAliveTime,  //생존 유지시간

   TimeUnit unit,  //생존 유지 시간의 단위

   BlockingQueue workQueue; //태스크 큐 유형

);




핵심풀 크기

스레드 풀에 포함되는 스레드의 하한, 실제로 스레드풀은0개 스레드로부터

시작하지만, 핵심 풀 크기에 도달하면 스레드 개수는 하한 이하로 떨어지지않는다.

풀에서 작업자 스레드의 개수가 핵심 풀크기보다 적을때, 태스크가 큐에 추가되면, 작업을 기다리는 유휴스레드가 있는경우에도 새로운 스레드가 생성될 것이다. 일단 작업자 스레드의 개수가 핵심풀크기보다 같거나 많아지면,

큐가 가득찬경우 새로운 작업자 스레드가 생성된다. 즉 큐는 스레드 생성에 대한 우선권을 얻는다.



최대 풀 크기

동시에 실행할 수 있는 스레드의 최대개수.

최대 풀 크기에 도달한 후, 큐에 더해진 태스크는 태스크를 처리할  유휴 스레드가 사용가능해질 떄까지 큐에서 기다린다.



최대 유휴시간(생존 유지시간)

유휴 스레드는 처리하기 위해 들어오는 태스크를 준비하기 위해 활성 상태를 유지하지만, 생존시간이 설정된 경우 시스템은 비핵심 풀 스레드를 회수할 수 있다.

생존시간은 TimeUnit에 의해 설정된 시간 단위로 측정된다.



태스크 큐 유형

Task가 작업자 스레드에 의해 처리될 수 있을때까지 소비자에 의해 더지핸 Task를 보유하는 BlockingQueue의 구현.



- 스레드 풀 설계

스레드 풀은 동시에 백그라운드 Task를 실행해야 하는 스레드를 관리하는데 

도움을 주지만, 사용자는 여전히 제한된 자원 소비와 높은 처리량을 얻기 위해 현명하게

설정해야 한다. 기본적인 목표는 필요한 것보다 더 많은 메모리를 사용하지 않고

하드웨어에 의해 허용되는 가장높은 속도로 작업을 처리하는 스레드 풀을 생성하는 것이다.




크기나스레드풀의 최대크기를 정한다. 스레드의 최대 개수가 너무작으면

충분한 속도로 큐에서 태스크를 꺼내지않아 성능저하될수있따.


예를들어 모든 스레드가 긴 I/O연산을 실행하는 경우, I/O 동작이 완료될떄까지 실행시간을 얻지 못하고 

기다리는 짧은 수명의 태스크가 있을 수 있다.


반면 너무 많은 스레드도 CPU가 실행 대신 스레드의 전환에 많은 시간을 사용해야하므로 성능에 부정적인 영향을 미칠 수 있다.


스레드 풀의 크기는 하부의 하드웨어, 좀더 정확히말해 사용가능한 CPU 개수를 기준으로 하는것이 좋다. 안드로이드는 Runtime 클래스에서 CPU개수를 알아낼 수 있다.


int N  = Runtime.getRuntume().availableProcessors();


N은 실제로 동시에 실행될 수 있는 태스크의 최대 개수다.


스레드 풀의크기 N은 독립적이고 비차단적인 태스크들의 운용하기에 충분할수있다.

(고도의 연산을 요하는등)



역동성



제한 또는 무제한 태스크 큐

스레드풀은 일반적으로 제한 또는 무제한 태스크 큐와 함꼐 사용한다.

무제한 큐는 무한증가할 수 있어서 메모리가 고갈할 수 있는 반면, 제한 큐의 자원소비는

더 잘관리될 수 있다 한편 제한 큐는 그 크기와 포화정책을 모두 준비해야하낟.

포화정책이란 거부된 태스크를 생산자가 어떻게 처리할지를 뜻한다.



제한 또는 무제한의 큐를 구현한 것이 LinkedBlockingQueue, PriorityBlockingQueue, ArrayBlockingQueue다.

뒤의 두가지 큐는 제한 큐고, 첫번째 큐는 기본적으로 무제한 큐지만 제한 큐로 구성할 수 있다.


스레드 풀


Thread pool은 task 큐와, 작업자 스레드 집합의 조합.


작업자 스레드는 생산자-소비자 구조를 형성한다.


생산자는 큐에 Task를 추가하고, 작업자 스레드는 새로운 백그라운드 실행을

수행할 준비가 된 유휴 스레드가 있을 때마다 Task를 소비한다.




작업자 스레드 풀은, 태스크를 실행하는 활성 스레드와 실행을 기다리는 유휴 스레드를 모두포함할 수 있다.



스레드 풀의 장점

- 작업자 스레드는 실행할 다음 태스크를 기다리기 위해서, 살아 있을 수 있음. 이는

스레드가 매 태스크를 위해 생성과 파괴(오버헤드 증가)될 필요 없다는 것을 의미한다.


- 스레드 풀은 스레드의 최대개수로 정의된다. 이는 응용프로그램 메모리를 소비하는

백그라운드 스레드 수가 많아져서 스레드 풀에 과부하가 걸리는것을 막기 위해서다.


-모든 작업자 스레드의 생명주기는 스레드 풀 생명주기에 의해 제어된다.



미리 정의된 스레드 풀

Executor 프레임워크는 Executors 팩토리 클래스에서 만들어진 미리 정의된 스레드 풀 유형을 포함한다.


- 고정 크기

고정크기 스레드 풀은, 사용자가 정의한 개수의 작업자 스레드를 유지한다.

종료된 스레드는 작업자 스레드의 일정 스레드 수를 유지하기 위해 새로운 스레드로 대체딘다.

고정된 풀 유형은 Executors.nexFixedThreadPool(n) 으로 생성되며 n은 스레드 수다.


이 스레드 풀 유형은 무한한 태스크 큐를 사용한다. 즉 새로운 태스크가 추가로 더해지면 큐가 자유롭게 증가할수있다.

그러므로 생산자는 태스크를 삽입하는데 실패하지 않는다.



- 동적 크기

동적 크기의 스레드 풀, 즉 캐시된 스레드 풀은 처리할 태스크가 있을 때 새로운

스레드를 만든다. 유휴 스레드는 실행할 새로운 태스크를 60초간 기다리고, 태스크 큐가 비어있는

경우 종료된다. 따라서 스레드 풀은 실행할 태스크 수와 함꼐 늘어나고 줄어든다.

Executors.newCachedThreadPool()로 생성한다.




- 싱글스레드 실행자

이 풀은 큐에서 task를 처리하기위해 하나의 작업자 스레드를 가진다.

태스크는 차례대로 실행되고 스레드 안전이 침해될수없다.

Executors.newSingleThreadExecutor()로 생성한다.

+ Recent posts