스레드 부족 데드락
- 단일 스레드로 동작하는 Excutor에서 다른 작업을 큐에 등록하고 해당 작업이 실행된 결과를 가져다 사용하는 작업을 실행시키면, 이전 작업이 추가한 두 번째 작업은 큐에 쌓인 상태로 이전 작업이 끝나기를 기다릴 것이고 이전 작업은 추가된 작업이 실행되어 그 결과를 알려주기를 기다릴 것이기 때문에 데드락이 발생함
- 스레드 풀의 크기가 크더라도 실행되는 모든 스레드가 큐에 쌓여 아직 실행되지 ㅇ않은 작업의 결과를 받으려고 대기 중이라면 위와 동일한 상황이 발생할 수 있으며 이런 현상을 스레드 부족 데드락이라 함
- 특정 자원을 확보하고자 계속해서 대기하거나 풀 내부의 다른 작업이 실행돼야 알 수 있는 조건이 만족하기를 기다리는 것처럼 끝없이 계쏙 대기할 가능성이 있는 기능을 사용하는 작업이 풀에 등록된 경우 언제든 발생 가능
스레드 풀 크기 조절
- 스레드 풀의 크기가 너무 크게 설정되어 있다면 스레드는 CPU나 메모리 등의 자원을 조금이라도 더 확보하기 위해 경쟁하게 되고, 그러다 보면 CPU에는 부하가 걸리고 메모리는 모자라 금방 자원 부족에 시달림
- 반대로 스레드 풀의 크기가 너무 작다면 작업량은 계속해서 쌓여 CPU나 메모리는 남아돌면서 작업 처리 속도가 떨어질 수 있음
- 스레드 풀의 크기를 적절하게 산정하려면 현재 컴퓨터 환경이 어느 정도인지 확인해야 하고, 확보하고 있는 자원의 양도 알아야 하며, 해야 할 작업이 어떻게 동작하는지도 정확히 알아야 함
- 스레드 풀의 크기는 다음 수식으로 구할 수 있음
N-thread- = N-cpu- * U-cpu- * ( 1 + W / C )U-cpu- = 목표로 하는 CPU 활용도, 0~1 사이 값
W/C = 작업 시간 대비 대기 시간의 비율
ThreadPoolExecutor
- Executor에 들어있는 newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool과 같은 팩토리 메서드에서 생성해주는 Executor에 대한 기본적인 구현이 되어있는 클래스
스레드 풀에서 작업을 쌓아둘 큐에 적용할 수 있는 전략
- 큐에 크기 제한을 두지 않는 방법
- 큐의 크기를 제한하는 방법: 자원 사용량을 한정시킬 수 있다는 장점이 있지만 큐가 가득 찼을 때 새로운 작업을 등록하려는 상황을 어떻게 처리해야 하는지에 대한 문제가 생김
- 작업을 스레드에 직접 넘겨주는 방법: SynchronousQueue를 사용해 프로듀서에서 쌩성한 작업을 컨슈머인 스레드에게 직접 전달, 스레드 간에 작업을 넘겨주는 기능을 담당한다고 볼 수 있음, newCachedThreadPool에서 사용
집중 대응 정책
- 크기가 제한된 큐에 작업이 가득 차면 동작
- RejectedExecutionHandler — AbortPolicy, CallerRunsPolicy, DiscardPolicy, DiscardOldestPolicy
- 기본적으로 Abort Policy가 적용되며, execute 메서드에서 RuntimeException을 상속받은 RejectedExecutioonException을 던짐
- Discard Policy는 큐에 작업을 더 이상 쌓을 수 없다면 방금 추가시키려고 했던 정책을 아무 반응 없이 제거
- DiscardOldestPolicy는 큐에 쌓은 항목 중 가장 오래되어 다음번에 실행될 예정이던 작업을 제거하고, 추가하고자 했던 작업을 큐에 다시 추가
- CallerRunsPolicy는 작업을 제거해 버리거나 예외를 던지지 않으면서 큐의 크기를 초과하는 작업을 프로듀서에게 거꾸로 넘겨 작업 추가 속도를 늦출 수 있도록 일종의 속도 조절 방법으로 사용, 새로 등록하려고 했던 작업을 스레드 풀의 작업 스레드로 실행하지 않고, execute 메서드를 호출해 작업을 등록하려 했던 스레드에서 실행