item 80) 스레드보다는 실행자, 태스크, 스트림을 애용하라
실행자 서비스
java.util.concurrent 패키지에는 동시성 제어와 관련된 클래스들을 담고 있다.
특히 실행자 프레임워크라고 하는 인터페이스 기반 유연한 태스크 실행 기능을 담고 있다.
아래와 같이 간단하게 작업 큐를 생성하고 실행할 수 있다.
주요 기능
get() 메서드를 통해 태스크가 완료되기를 기다린다.
태스크들 중 하나라도 혹은 모든 태스크가 완료되기를 기다린다.
실행자 서비스가 종료되기를 기다린다.
완료된 태스크의 결과를 차례로 받는다.
태스크를 특정 시간 혹은 주기적으로 실행하게 한다.
큐를 둘 이상의 스레드가 처리하게 하고 싶다면 다른 정적 팩터리를 이용해 다른 종류의 실행자 서비스를 생성하면 된다. 스레드의 개수는 고정할 수 있고(newFixedThreadPool), 필요에 따라 늘어나거나 줄어들게도(newCachedThreadPool) 설정할 수 있다.
실행자 서비스 생성 방법
java.util.concurrent.Executors
의 정적 팩터리들을 이용하거나, ThreadPoolExecutor 클래스를 직접 사용해도 된다.
ThreadPoolExecutor
자바에서 ThreadPool 사용을 위해 지원하는 클래스
ThreadPool: 쓰레드를 미리 만들어 놓고 재사용하는 방식
평소에는 corePoolSize만큼 기본 스레드 개수를 유지하다가, 모든 코어 쓰레드가 바쁘고 내부 큐도 꽉차면 maximumPooSize 까지 개수를 늘린다.
더 알아보려면 링크를 참고하는 것도 좋다. https://keichee.tistory.com/388
newCachedThreadPool vs newFixedThreadPool
두 실행자 서비스 생성 방법은 Executors의 정적 팩터리에서 제공되는 방식이다.
newCachedThreadPool
작은 프로그램이나 가벼운 서버에 적합
요청받은 태스크들을 큐에 쌓지 않고 즉시 스레드에 위임해 실행
사용 가능한 스레드가 없다면 새로 스레드를 생성하기 때문에 CPU 사용률이 100%에 치닫는 상황이 생길 수 있다. 새로운 태스크가 도착할 때마다 가용 스레드가 없으니 다른 스레드를 계속 생성하면 상황은 더욱 악화될 것이다.
newFixedThreadPool
무거운 프로덕션 서버에 적합
스레드 개수를 고정해두고 사용
정리
작업 큐를 손수 만들거나 스레드를 직접 다루는 일은 삼가야 한다.
스레드를 직접 다루면 스레드가 작업 단위와 수행 매커니즘 역할을 모두 수행하지만, 실행자 프레임워크를 사용하면 작업 단위와 실행 매커니즘이 분리된다.
실행자 서비스에 태스크 수행을 맡기면, 실행자 프레임워크가 원하는 태스크 수행 정책에 따라 작업 수행을 담당해주고, 실제 작업 수행은 각 스레드에 위임되는 것이다.
실행자 프레임워크인 ForkJoinPool을 구성하는 스레드들은 태스크들(ForkJoinTask)을 처리하며, 수행이 끝난 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수 있다. 이를 통해 모든 스레드를 바쁘게 움직여 CPU를 최대한 활용해 높은 처리량과 낮은 지연시간을 가질 수 있다.
Last updated