Executors.newCachedThreadPool () 및 Executors.newFixedThreadPool ()


답변:


202

문서 가이 두 기능의 차이점과 사용법을 잘 설명한다고 생각합니다.

newFixedThreadPool

공유 언 바운드 큐에서 작동하는 고정 된 수의 스레드를 재사용하는 스레드 풀을 작성합니다. 어느 시점에서나 최대 nThreads 스레드는 활성 처리 작업입니다. 모든 스레드가 활성화 될 때 추가 태스크가 제출되면 스레드가 사용 가능할 때까지 큐에서 대기합니다. 종료 전에 실행 중 실패로 인해 스레드가 종료되면 후속 작업을 실행하는 데 필요한 경우 새 스레드가 대신 사용됩니다. 풀의 스레드는 명시 적으로 종료 될 때까지 존재합니다.

newCachedThreadPool

필요에 따라 새 스레드를 작성하는 스레드 풀을 작성하지만 사용 가능한 경우 이전에 구성된 스레드를 재사용합니다. 이러한 풀은 일반적으로 많은 단기 비동기 작업을 실행하는 프로그램의 성능을 향상시킵니다. 실행 호출은 사용 가능한 경우 이전에 생성 된 스레드를 재사용합니다. 사용 가능한 기존 스레드가 없으면 새 스레드가 작성되어 풀에 추가됩니다. 60 초 동안 사용되지 않은 스레드는 종료되고 캐시에서 제거됩니다. 따라서 오랫동안 유휴 상태 인 풀은 리소스를 소비하지 않습니다. ThreadPoolExecutor 생성자를 사용하여 속성은 비슷하지만 세부 정보 (예 : 시간 초과 매개 변수)가 다른 풀을 만들 수 있습니다.

리소스 측면에서는 newFixedThreadPool모든 스레드가 명시 적으로 종료 될 때까지 계속 실행됩니다. 에서 newCachedThreadPool60 초간 사용되지 않았던 thread 종료하고 캐시에서 제거.

이 상황에서 자원 소비는 상황에 따라 크게 달라집니다. 예를 들어, 장시간 실행되는 작업이 많은 경우을 제안합니다 FixedThreadPool. 에 관해서는 CachedThreadPool, 워드 프로세서는 "이 풀은 일반적으로 단기의 비동기 태스크를 다수 실행하는 프로그램의 성능이 향상됩니다"고 말한다.


1
예 나는 문서를 겪었습니다. 문제는 ... 고정되었습니다 ThreadPool이 3 스레드에서 메모리 부족 오류를 일으 킵니다 .. cachedPool이 내부적으로 단일 스레드 만 생성합니다 .. 힙 크기를 늘리면 동일합니다. 둘 다를위한 성과.
hakish 2016 년

1
ThreadPool에 Threadfactory를 제공하고 있습니까? 내 생각에 가비지 수집되지 않는 스레드에 상태를 저장하고있을 수 있습니다. 그렇지 않은 경우 프로그램이 힙 제한 크기에 너무 가깝게 실행되어 3 개의 스레드를 만들면 OutOfMemory가 발생합니다. 또한 cachedPool이 내부적으로 단일 스레드 만 작성하는 경우 이는 태스크가 동기화되어 실행 중임을 나타냅니다.
bruno conde

@brunoconde 것처럼 밖으로 @Louis F. 포인트 newCachedThreadPool일부 발생할 수 있습니다 심각한 당신은 모든 컨트롤을두고 있기 때문에 문제를 thread pool서비스가 같은 다른 사람들과 협력하고 호스트 다른 사람으로 인해 오랜 CPU 대기에 충돌이 발생할 수 있습니다. newFixedThreadPool이런 종류의 시나리오에서는 더 안전 할 수 있다고 생각 합니다. 또한이 게시물 은 그들 사이의 가장 두드러진 차이점을 분명히합니다.
Hearen

75

다른 답변을 완성하기 위해 Joshua Bloch의 Chapter 10, 항목 68의 Effective Java, 2nd Edition을 인용하고 싶습니다.

"특정 응용 프로그램의 실행 프로그램 서비스를 선택하는 것은 당신이 작성하는 경우. 까다로울 수있다 작은 프로그램 , 또는 부하가 적은 서버를 사용하여 Executors.new-CachedThreadPool을 하는 것이 일반적입니다. 구성이 필요없고 일반적으로" 옳은 일." 그러나 캐시 된 스레드 풀은 좋은 선택하지 A에 대한 과도하게로드 프로덕션 서버 !

안에 캐시 된 스레드 풀 , 제출 된 작업은 대기하지 않습니다 하지만 즉시 실행 스레드에 넘겨. 사용 가능한 스레드가 없으면 새 스레드가 작성 됩니다. 서버가 너무 많이로드되어 모든 CPU가 완전히 활용되고 더 많은 작업이 도착하면 더 많은 스레드가 생성되어 문제가 더 악화됩니다.

따라서 로드 가 많은 프로덕션 서버 에서는 최대 수의 제어를 위해 스레드 수가 고정 된 풀을 제공하거나 ThreadPoolExecutor 클래스를 직접 사용하는 Executors.newFixedThreadPool 을 사용하는 것이 훨씬 좋습니다 . "


15

소스 코드 를 보면 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>());
}

1
정확하게, 상한 상한을 갖는 캐시 된 스레드 실행기, 즉 5-10 분의 유휴 수확은 대부분의 경우에 완벽합니다.
Agoston Horvath

12

호출 가능한 / 실행 가능한 작업 의 무제한 큐에 대해 걱정하지 않으면 그 중 하나를 사용할 수 있습니다. 브루노가 제안한 것처럼, 나는 이 둘 이상을 선호 newFixedThreadPool합니다 newCachedThreadPool.

그러나 ThreadPoolExecutornewFixedThreadPool또는newCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

장점 :

  1. BlockingQueue 크기를 완전히 제어 할 수 있습니다 . 이전 두 옵션과 달리 제한이 없습니다. 시스템에 예상치 못한 난기류가있을 때 보류중인 Callable / Runnable 작업이 크게 쌓여서 메모리 부족 오류가 발생하지 않습니다.

  2. 사용자 지정 거부 처리 정책을 구현 하거나 다음 정책 중 하나를 사용할 수 있습니다 .

    1. 기본값 ThreadPoolExecutor.AbortPolicy에서 핸들러는 거부시 런타임 RejectedExecutionException을 발생시킵니다.

    2. 에서 ThreadPoolExecutor.CallerRunsPolicy실행하는 스레드 자체가 작업을 실행합니다. 이는 새로운 작업 제출 속도를 늦추는 간단한 피드백 제어 메커니즘을 제공합니다.

    3. 에서 ThreadPoolExecutor.DiscardPolicy실행할 수없는 작업은 간단히 삭제됩니다.

    4. 에서 ThreadPoolExecutor.DiscardOldestPolicy집행자가 종료되지 않은 경우, 작업 큐의 선두에있는 태스크가 삭제되어 실행이 시도됩니다 (이 반복되는 원인이 다시 실패 할 수있다.)

  3. 아래 사용 사례에 대해 사용자 정의 스레드 팩토리를 구현할 수 있습니다.

    1. 보다 설명적인 스레드 이름을 설정하려면
    2. 스레드 데몬 상태를 설정하려면
    3. 스레드 우선 순위를 설정하려면

11

바로 그, Executors.newCachedThreadPool()여러 클라이언트와 동시 요청을 서비스의 서버 코드에 대한 좋은 선택이 아니다.

왜? 기본적으로 두 가지 (관련) 문제가 있습니다.

  1. 제한이 없습니다. 즉, 서비스에 더 많은 작업을 주입하여 (DoS 공격) 누구나 JVM을 무너 뜨릴 수있는 기회를 열게됩니다. 스레드는 무시할 수없는 양의 메모리를 소비하고 진행중인 작업에 따라 메모리 소비를 증가 시키므로 다른 회로 차단기가없는 경우 서버를 이런 방식으로 쉽게 교체 할 수 있습니다.

  2. 끝없는 문제는 집행자가 앞에 있다는 사실에 의해 악화됩니다. SynchronousQueue즉, 작업 제공자와 스레드 풀 사이에 직접적인 전달이 있음을 의미합니다. 기존의 모든 스레드가 사용중인 경우 각 새 작업은 새 스레드를 만듭니다. 이것은 일반적으로 서버 코드에 대한 나쁜 전략입니다. CPU가 포화 상태가되면 기존 작업을 완료하는 데 시간이 더 걸립니다. 더 많은 작업이 제출되고 더 많은 스레드가 생성되므로 작업을 완료하는 데 시간이 오래 걸립니다. CPU가 가득 차면 더 많은 스레드가 서버에 필요한 것은 아닙니다.

내 추천은 다음과 같습니다.

고정 크기 스레드 풀 Executors.newFixedThreadPool 또는 ThreadPoolExecutor를 사용하십시오. 설정된 최대 스레드 수;


6

ThreadPoolExecutor클래스는 여러 Executors팩토리 메소드 에서 리턴되는 실행 프로그램의 기본 구현입니다 . 이제 고정캐시 스레드 풀에 접근하십시오 ThreadPoolExecutor.

ThreadPoolExecutor

이 클래스 의 주요 생성자 는 다음과 같습니다.

public ThreadPoolExecutor(
                  int corePoolSize,
                  int maximumPoolSize,
                  long keepAliveTime,
                  TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,
                  ThreadFactory threadFactory,
                  RejectedExecutionHandler handler
)

코어 풀 크기

corePoolSize타겟 스레드 풀의 최소 사이즈를 결정한다. 실행할 작업이 없더라도 구현은 해당 크기의 풀을 유지합니다.

최대 수영장 크기

maximumPoolSize한 번에 활성화 할 수있는 스레드의 최대 수입니다.

스레드 풀이 커지고 corePoolSize임계 값 보다 커지면 실행자는 유휴 스레드를 종료하고 corePoolSize다시 도달 할 수 있습니다 . allowCoreThreadTimeOuttrue 인 경우 실행 프로그램이 코어 풀 스레드가 keepAliveTime임계 값 보다 유휴 상태 인 경우 코어 풀 스레드를 종료 할 수도 있습니다 .

결론은 스레드가 keepAliveTime임계 값 이상으로 유휴 상태로 유지되면 스레드 가 필요하지 않기 때문에 종료 될 수 있다는 것입니다.

큐잉

새 작업이 시작되고 모든 코어 스레드가 사용되면 어떻게됩니까? 새 작업이 해당 BlockingQueue<Runnable>인스턴스 내에서 대기 합니다. 스레드가 사용 가능 해지면 대기중인 태스크 중 하나를 처리 할 수 ​​있습니다.

BlockingQueueJava 에는 다양한 인터페이스 구현이 있으므로 다음과 같은 다른 큐잉 접근 방식을 구현할 수 있습니다.

  1. 바운드 대기열 : 새로운 작업이 바운드 작업 대기열에 대기됩니다.

  2. 무제한 대기열 : 새로운 작업은 무제한 작업 대기열 내에 대기합니다. 따라서이 큐는 힙 크기가 허용하는만큼 커질 수 있습니다.

  3. 동기식 핸드 오프 :를 사용 SynchronousQueue하여 새 작업을 대기열에 넣을 수도 있습니다 . 이 경우 새 작업을 대기열에 넣을 때 다른 스레드가 이미 해당 작업을 기다리고 있어야합니다.

작품 제출

ThreadPoolExecutor새로운 작업을 수행하는 방법은 다음과 같습니다 .

  1. corePoolSize실행중인 스레드 수보다 적은 수의 경우 주어진 작업을 첫 번째 작업으로하여 새 스레드를 시작하려고합니다.
  2. 그렇지 않으면 BlockingQueue#offer메소드를 사용하여 새 태스크를 큐에 넣습니다 . offer큐가 즉시 반환 가득하고 있다면 방법은 차단하지 않습니다 false.
  3. 새 작업을 대기열에 넣지 못하면 (즉 ,을 offer반환 false)이 작업을 첫 번째 작업으로하여 스레드 풀에 새 스레드를 추가하려고합니다.
  4. 새 스레드를 추가하지 못하면 실행 프로그램이 종료되었거나 포화 상태입니다. 어느 쪽이든, 새로운 작업은 제공된를 사용하여 거부됩니다 RejectedExecutionHandler.

고정 스레드 풀과 캐시 스레드 풀의 주요 차이점은 다음 세 가지 요소로 요약됩니다.

  1. 코어 풀 크기
  2. 최대 수영장 크기
  3. 큐잉
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| 풀 타입 | 코어 크기 | 최대 크기 | 큐잉 전략 |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| 고정 | 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>());
}

보다시피 :

  • 스레드 풀은 0 스레드에서으로 증가 할 수 있습니다 Integer.MAX_VALUE. 실제로 스레드 풀은 제한이 없습니다.
  • 스레드가 1 분 이상 유휴 상태이면 종료 될 수 있습니다. 따라서 스레드가 너무 유휴 상태 인 경우 풀이 축소 될 수 있습니다.
  • 새 작업이 들어올 때 할당 된 모든 스레드가 사용되면 다른 스레드 SynchronousQueue에 수락 할 수있는 사람이없는 경우 항상 새 작업을 제공 할 수 없으므로 새 스레드가 생성 됩니다.

언제 하나를 사용해야합니까? 리소스 활용 측면에서 어떤 전략이 더 낫습니까?

예측 가능한 단기 실행 작업이 많을 때 사용하십시오.



1

몇 가지 빠른 테스트를 수행 한 결과는 다음과 같습니다.

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를 사용하는 경우 :

스레드는 최소 크기에서 최대 크기로 증가하지 않습니다. 즉, 스레드 풀의 크기는 최소 크기로 고정됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.