newCachedThreadPool()
대 newFixedThreadPool()
언제 하나를 사용해야합니까? 리소스 활용 측면에서 어떤 전략이 더 낫습니까?
newCachedThreadPool()
대 newFixedThreadPool()
언제 하나를 사용해야합니까? 리소스 활용 측면에서 어떤 전략이 더 낫습니까?
답변:
문서 가이 두 기능의 차이점과 사용법을 잘 설명한다고 생각합니다.
공유 언 바운드 큐에서 작동하는 고정 된 수의 스레드를 재사용하는 스레드 풀을 작성합니다. 어느 시점에서나 최대 nThreads 스레드는 활성 처리 작업입니다. 모든 스레드가 활성화 될 때 추가 태스크가 제출되면 스레드가 사용 가능할 때까지 큐에서 대기합니다. 종료 전에 실행 중 실패로 인해 스레드가 종료되면 후속 작업을 실행하는 데 필요한 경우 새 스레드가 대신 사용됩니다. 풀의 스레드는 명시 적으로 종료 될 때까지 존재합니다.
필요에 따라 새 스레드를 작성하는 스레드 풀을 작성하지만 사용 가능한 경우 이전에 구성된 스레드를 재사용합니다. 이러한 풀은 일반적으로 많은 단기 비동기 작업을 실행하는 프로그램의 성능을 향상시킵니다. 실행 호출은 사용 가능한 경우 이전에 생성 된 스레드를 재사용합니다. 사용 가능한 기존 스레드가 없으면 새 스레드가 작성되어 풀에 추가됩니다. 60 초 동안 사용되지 않은 스레드는 종료되고 캐시에서 제거됩니다. 따라서 오랫동안 유휴 상태 인 풀은 리소스를 소비하지 않습니다. ThreadPoolExecutor 생성자를 사용하여 속성은 비슷하지만 세부 정보 (예 : 시간 초과 매개 변수)가 다른 풀을 만들 수 있습니다.
리소스 측면에서는 newFixedThreadPool
모든 스레드가 명시 적으로 종료 될 때까지 계속 실행됩니다. 에서 newCachedThreadPool
60 초간 사용되지 않았던 thread 종료하고 캐시에서 제거.
이 상황에서 자원 소비는 상황에 따라 크게 달라집니다. 예를 들어, 장시간 실행되는 작업이 많은 경우을 제안합니다 FixedThreadPool
. 에 관해서는 CachedThreadPool
, 워드 프로세서는 "이 풀은 일반적으로 단기의 비동기 태스크를 다수 실행하는 프로그램의 성능이 향상됩니다"고 말한다.
다른 답변을 완성하기 위해 Joshua Bloch의 Chapter 10, 항목 68의 Effective Java, 2nd Edition을 인용하고 싶습니다.
"특정 응용 프로그램의 실행 프로그램 서비스를 선택하는 것은 당신이 작성하는 경우. 까다로울 수있다 작은 프로그램 , 또는 부하가 적은 서버를 사용하여 Executors.new-CachedThreadPool을 하는 것이 일반적입니다. 구성이 필요없고 일반적으로" 옳은 일." 그러나 캐시 된 스레드 풀은 좋은 선택하지 A에 대한 과도하게로드 프로덕션 서버 !
안에 캐시 된 스레드 풀 , 제출 된 작업은 대기하지 않습니다 하지만 즉시 실행 스레드에 넘겨. 사용 가능한 스레드가 없으면 새 스레드가 작성 됩니다. 서버가 너무 많이로드되어 모든 CPU가 완전히 활용되고 더 많은 작업이 도착하면 더 많은 스레드가 생성되어 문제가 더 악화됩니다.
따라서 로드 가 많은 프로덕션 서버 에서는 최대 수의 제어를 위해 스레드 수가 고정 된 풀을 제공하거나 ThreadPoolExecutor 클래스를 직접 사용하는 Executors.newFixedThreadPool 을 사용하는 것이 훨씬 좋습니다 . "
소스 코드 를 보면 ThreadPoolExecutor를 호출하고 있음을 알 수 있습니다 . 내부 및 속성 설정 요구 사항을보다 잘 제어 할 수 있도록 하나를 만들 수 있습니다.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
호출 가능한 / 실행 가능한 작업 의 무제한 큐에 대해 걱정하지 않으면 그 중 하나를 사용할 수 있습니다. 브루노가 제안한 것처럼, 나는 이 둘 이상을 선호 newFixedThreadPool
합니다 newCachedThreadPool
.
그러나 ThreadPoolExecutor 는 newFixedThreadPool
또는newCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
장점 :
BlockingQueue 크기를 완전히 제어 할 수 있습니다 . 이전 두 옵션과 달리 제한이 없습니다. 시스템에 예상치 못한 난기류가있을 때 보류중인 Callable / Runnable 작업이 크게 쌓여서 메모리 부족 오류가 발생하지 않습니다.
사용자 지정 거부 처리 정책을 구현 하거나 다음 정책 중 하나를 사용할 수 있습니다 .
기본값 ThreadPoolExecutor.AbortPolicy
에서 핸들러는 거부시 런타임 RejectedExecutionException을 발생시킵니다.
에서 ThreadPoolExecutor.CallerRunsPolicy
실행하는 스레드 자체가 작업을 실행합니다. 이는 새로운 작업 제출 속도를 늦추는 간단한 피드백 제어 메커니즘을 제공합니다.
에서 ThreadPoolExecutor.DiscardPolicy
실행할 수없는 작업은 간단히 삭제됩니다.
에서 ThreadPoolExecutor.DiscardOldestPolicy
집행자가 종료되지 않은 경우, 작업 큐의 선두에있는 태스크가 삭제되어 실행이 시도됩니다 (이 반복되는 원인이 다시 실패 할 수있다.)
아래 사용 사례에 대해 사용자 정의 스레드 팩토리를 구현할 수 있습니다.
바로 그, Executors.newCachedThreadPool()
여러 클라이언트와 동시 요청을 서비스의 서버 코드에 대한 좋은 선택이 아니다.
왜? 기본적으로 두 가지 (관련) 문제가 있습니다.
제한이 없습니다. 즉, 서비스에 더 많은 작업을 주입하여 (DoS 공격) 누구나 JVM을 무너 뜨릴 수있는 기회를 열게됩니다. 스레드는 무시할 수없는 양의 메모리를 소비하고 진행중인 작업에 따라 메모리 소비를 증가 시키므로 다른 회로 차단기가없는 경우 서버를 이런 방식으로 쉽게 교체 할 수 있습니다.
끝없는 문제는 집행자가 앞에 있다는 사실에 의해 악화됩니다. SynchronousQueue
즉, 작업 제공자와 스레드 풀 사이에 직접적인 전달이 있음을 의미합니다. 기존의 모든 스레드가 사용중인 경우 각 새 작업은 새 스레드를 만듭니다. 이것은 일반적으로 서버 코드에 대한 나쁜 전략입니다. CPU가 포화 상태가되면 기존 작업을 완료하는 데 시간이 더 걸립니다. 더 많은 작업이 제출되고 더 많은 스레드가 생성되므로 작업을 완료하는 데 시간이 오래 걸립니다. CPU가 가득 차면 더 많은 스레드가 서버에 필요한 것은 아닙니다.
내 추천은 다음과 같습니다.
고정 크기 스레드 풀 Executors.newFixedThreadPool 또는 ThreadPoolExecutor를 사용하십시오. 설정된 최대 스레드 수;
이 ThreadPoolExecutor
클래스는 여러 Executors
팩토리 메소드 에서 리턴되는 실행 프로그램의 기본 구현입니다 . 이제 고정 및 캐시 스레드 풀에 접근하십시오 ThreadPoolExecutor
.
이 클래스 의 주요 생성자 는 다음과 같습니다.
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
는 corePoolSize
타겟 스레드 풀의 최소 사이즈를 결정한다. 실행할 작업이 없더라도 구현은 해당 크기의 풀을 유지합니다.
는 maximumPoolSize
한 번에 활성화 할 수있는 스레드의 최대 수입니다.
스레드 풀이 커지고 corePoolSize
임계 값 보다 커지면 실행자는 유휴 스레드를 종료하고 corePoolSize
다시 도달 할 수 있습니다 . allowCoreThreadTimeOut
true 인 경우 실행 프로그램이 코어 풀 스레드가 keepAliveTime
임계 값 보다 유휴 상태 인 경우 코어 풀 스레드를 종료 할 수도 있습니다 .
결론은 스레드가 keepAliveTime
임계 값 이상으로 유휴 상태로 유지되면 스레드 가 필요하지 않기 때문에 종료 될 수 있다는 것입니다.
새 작업이 시작되고 모든 코어 스레드가 사용되면 어떻게됩니까? 새 작업이 해당 BlockingQueue<Runnable>
인스턴스 내에서 대기 합니다. 스레드가 사용 가능 해지면 대기중인 태스크 중 하나를 처리 할 수 있습니다.
BlockingQueue
Java 에는 다양한 인터페이스 구현이 있으므로 다음과 같은 다른 큐잉 접근 방식을 구현할 수 있습니다.
바운드 대기열 : 새로운 작업이 바운드 작업 대기열에 대기됩니다.
무제한 대기열 : 새로운 작업은 무제한 작업 대기열 내에 대기합니다. 따라서이 큐는 힙 크기가 허용하는만큼 커질 수 있습니다.
동기식 핸드 오프 :를 사용 SynchronousQueue
하여 새 작업을 대기열에 넣을 수도 있습니다 . 이 경우 새 작업을 대기열에 넣을 때 다른 스레드가 이미 해당 작업을 기다리고 있어야합니다.
ThreadPoolExecutor
새로운 작업을 수행하는 방법은 다음과 같습니다 .
corePoolSize
실행중인 스레드 수보다 적은 수의 경우 주어진 작업을 첫 번째 작업으로하여 새 스레드를 시작하려고합니다.BlockingQueue#offer
메소드를 사용하여 새 태스크를 큐에 넣습니다
. offer
큐가 즉시 반환 가득하고 있다면 방법은 차단하지 않습니다 false
.offer
반환 false
)이 작업을 첫 번째 작업으로하여 스레드 풀에 새 스레드를 추가하려고합니다.RejectedExecutionHandler
.고정 스레드 풀과 캐시 스레드 풀의 주요 차이점은 다음 세 가지 요소로 요약됩니다.
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | 풀 타입 | 코어 크기 | 최대 크기 | 큐잉 전략 | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | 고정 | n (고정) | n (고정) | 무제한`LinkedBlockingQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | 캐시 | 0 | 정수 .MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
작동
방식은 다음과 같습니다 .
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
보다시피 :
OutOfMemoryError
.언제 하나를 사용해야합니까? 리소스 활용 측면에서 어떤 전략이 더 낫습니까?
리소스 관리 목적으로 동시 작업 수를 제한하려는 경우 고정 크기 스레드 풀이 적합한 후보로 보입니다 .
예를 들어, 실행기를 사용하여 웹 서버 요청을 처리하려는 경우 고정 실행자는 요청 버스트를보다 합리적으로 처리 할 수 있습니다.
더 나은 자원 관리를 위해, 매우 사용자 정의 만들 것을 추천 ThreadPoolExecutor
바운드와 BlockingQueue<T>
합리적인 결합 구현 RejectedExecutionHandler
.
Executors.newCachedThreadPool()
작동 방식은 다음과 같습니다 .
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
보다시피 :
Integer.MAX_VALUE
. 실제로 스레드 풀은 제한이 없습니다.SynchronousQueue
에 수락 할 수있는 사람이없는 경우 항상 새 작업을 제공 할 수 없으므로 새 스레드가 생성 됩니다.언제 하나를 사용해야합니까? 리소스 활용 측면에서 어떤 전략이 더 낫습니까?
예측 가능한 단기 실행 작업이 많을 때 사용하십시오.
Javadoc에 명시된 바와 같이 수명이 짧은 비동기 작업이있는 경우에만 newCachedThreadPool을 사용해야합니다. 처리하는 데 시간이 오래 걸리는 작업을 제출하면 너무 많은 스레드가 생성됩니다. newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ )에 장기 실행 작업을 더 빠른 속도로 제출하면 100 % CPU에 도달 할 수 있습니다 .
몇 가지 빠른 테스트를 수행 한 결과는 다음과 같습니다.
1) SynchronousQueue를 사용하는 경우 :
스레드가 최대 크기에 도달하면 다음과 같은 예외를 제외하고 새로운 작업이 거부됩니다.
스레드 "main"의 예외 java.util.concurrent.RejectedExecutionException : 태스크 java.util.concurrent.FutureTask@3fee733d가 java.util.concurrent.ThreadPoolExecutor@5acf9800에서 거부 됨 (실행 중, 풀 크기 = 3, 활성 스레드 = 3, 대기중인 태스크 = 0, 완료된 작업 = 0]
java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)에서
2) LinkedBlockingQueue를 사용하는 경우 :
스레드는 최소 크기에서 최대 크기로 증가하지 않습니다. 즉, 스레드 풀의 크기는 최소 크기로 고정됩니다.