IT이야기

실행자.newCacheThreadPool() 대 실행자.newFixed스레드풀()

cyworld 2022. 4. 14. 00:57
반응형

실행자.newCacheThreadPool() 대 실행자.newFixed스레드풀()

newCachedThreadPool()

하나 또는 다른 하나를 언제 사용해야 하는가?자원 활용 측면에서 어떤 전략이 더 나은가?

나는 문서들이 이 두 기능의 차이와 사용법을 꽤 잘 설명한다고 생각한다.

newFixedThreadPool

공유된 바인딩되지 않은 대기열에서 작동하는 고정된 수의 스레드를 재사용하는 스레드 풀 생성어느 시점에서든 nThreads 스레드는 활성 처리 태스크가 될 것이다.모든 스레드가 활성 상태일 때 추가 태스크가 제출되면 스레드를 사용할 수 있을 때까지 대기열에서 대기한다.종료 전 실행 중 실패로 인해 스레드가 종료되는 경우 후속 작업을 실행하기 위해 필요할 경우 새 스레드가 대체된다.풀의 스레드는 명시적으로 종료될 때까지 존재하게 된다.

newCachedThreadPool

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

자원의 측면에서는newFixedThreadPool명시적으로 종료될 때까지 모든 스레드가 실행되도록 유지한다.에서newCachedThreadPool60초 동안 사용하지 않은 스레드는 종료되고 캐시에서 제거된다.

이를 감안할 때 자원 소비는 상황에 매우 크게 좌우될 것이다.예를 들어, 만약 당신이 많은 장기 실행 작업을 가지고 있다면, 나는 다음 작업을 제안할 것이다.FixedThreadPool에 대해서는CachedThreadPool이 문서들은 "이 풀들은 일반적으로 많은 단명 비동기 작업을 실행하는 프로그램의 성능을 향상시킬 것이다"라고 말한다.

단지 다른 답들을 완성하기 위해서, 나는 Joshua Bloch가 쓴 Effective Java, 2판을 인용하고 싶다. 10장, 항목 68 :

"특정 애플리케이션에 대해 실행자 서비스를 선택하는 것은 까다로울 수 있다.작은 프로그램이나 가볍게 로드된 서버를 작성하는 경우, 실행자.new-CashedThreadPool은 구성을 요구하지 않으며 일반적으로 "올바른 작업을 수행"하기 때문에 일반적으로 좋은 선택이다.하지만 캐시된 스레드 풀은 부하가 많은 프로덕션 서버에는 적합하지 않다!

캐시된 스레드 풀에서 제출된 작업은 대기열에 있지 않고 실행을 위해 스레드로 즉시 넘겨진다.사용할 수 있는 스레드가 없으면스레드가 생성된다.서버의 부하가 너무 커서 모든 CPU가 충분히 활용되고 더 많은 작업이 도착하면 더 많은 스레드가 생성되어 문제가 더 악화될 뿐이다.

따라서 로드가 많은 프로덕션 서버에서는 실행기를 사용하는 것이 훨씬 더 낫다.newFixed스레드 수가 고정된 풀을 제공하거나 스레드풀Executor 클래스를 직접 사용하여 최대한의 제어를 할 수 있는 스레드풀."

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

클래스는 많은 시스템에서 반환되는 실행자에 대한 기본 구현이다.Executors공장 방식다음에서 고정캐시된 스레드 풀에 접근해 보십시오.ThreadPoolExecutor의 관점

스레드풀Executor

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

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

코어 풀 크기

는 대상 스레드 풀의 최소 크기를 결정한다.실행해야 할 과제가 없더라도 구현은 그 크기의 풀을 유지할 것이다.

최대 풀 크기

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

( )보다 커진다.corePoolSize임계값, 실행자는 유휴 스레드를 종료하고 에 도달할 수 있음corePoolSize다시. 만약allowCoreThreadTimeOut사실이며, 그러면 실행자는 코어 풀 스레드가 다음보다 많이 유휴 상태일 경우 코어 풀 스레드를 종료할 수도 있음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 | 동기화 큐 |+-----------+-----------+-------------------+---------------------------------+


고정 스레드 풀


Here's how the Excutors.newFixedThreadPool(n) works:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

보시다시피:

  • 스레드 풀 크기가 고정되어 있음
  • 수요가 많으면 성장하지 않는다.
  • 실이 꽤 오랫동안 유휴하면 줄어들지 않는다.
  • 모든 스레드가 장기간 실행되는 작업에 사용되며 도착률이 여전히 높다고 가정해 보십시오.실행자는 무한 대기열을 사용하고 있으므로 힙의 상당 부분을 사용할 수 있다.는 불하hauthado를 할 수 .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개에서 0개로 증가할 수 있음Integer.MAX_VALUE, 은 한이 없다실제로는 실 풀에 한이 없다.
  • 스레드가 1분 이상 공회전하면 종료될 수 있다.그래서 스레드가 너무 많이 유휴 상태로 남아 있으면 풀장이 줄어들 수 있다.
  • 새 작업이 들어오는 동안 할당된 모든 스레드가 사용되면 새 스레드를 생성하여 새 작업을SynchronousQueue그것을 받아들일 다른 사람이 없을 때 항상 실패한다!

하나 또는 다른 하나를 언제 사용해야 하는가?자원 활용 측면에서 어떤 전략이 더 나은가?

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

호출 가능/실행 가능 태스크의 제한 없는 대기열이 걱정되지 않는 경우 이 중 하나를 사용하십시오.Bruno의 제안대로, 나도 더 좋아한다.newFixedThreadPoolnewCachedThreadPool이 두 개에 걸쳐서

그러나 ThreadPoolExecutor는 다음 중 하나에 비해 더 유연한 기능을 제공한다.newFixedThreadPool또는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, 처리기가 런타임 거부됨을 발생시킴실행거부 시 예외.

    2. ThreadPoolExecutor.CallerRunsPolicy를 호출하는 스레드가 태스크를 실행한다.이것은 새로운 과제가 제출되는 속도를 늦추는 간단한 피드백 제어 메커니즘을 제공한다.

    3. ThreadPoolExecutor.DiscardPolicy실행될 수 없는 작업은 간단히 삭제된다.

    4. ThreadPoolExecutor.DiscardOldestPolicy실행자가 종료되지 않은 경우, 작업 대기열의 맨 위에 있는 태스크가 삭제된 다음 다시 실행이 시도된다(이 작업은 다시 실패하여 이 문제가 반복될 수 있음).

  3. 다음 사용 사례에 대해 사용자 지정 스레드 팩토리를 구현할 수 있다.

    1. 더 설명적인 스레드 이름을 설정하려면 다음과 같이 하십시오.
    2. 스레드 데몬 상태를 설정하려면 다음과 같이 하십시오.
    3. 스레드 우선 순위를 설정하려면 다음과 같이 하십시오.

그랬다.Executors.newCachedThreadPool()여러 클라이언트와 동시 요청을 처리하는 서버 코드에는 적합하지 않다.

왜냐고? 거기에는 기본적으로 두 가지 문제가 있다.

  1. 한없이 무한하다는 것은, 단순히 서비스에 더 많은 작업을 투입함으로써 JVM을 무력화시킬 수 있는 문을 열고 있다는 것을 의미한다(DoS 공격).스레드는 판독 불가능한 양의 메모리를 소비하고 또한 진행 중인 작업에 기반하여 메모리 소비량을 증가시키므로, 서버를 이런 식으로 쓰러뜨리기는 꽤 쉽다(다른 회로 차단기가 없는 한).

  2. 한없는 문제는 실행자가 앞장을 선다는 사실 때문에 더욱 악화된다.SynchronousQueue즉, 작업 전달자와 나사산 풀 사이에 직접적인 손길이 있다는 겁니다.기존 스레드가 모두 사용 중이면 새 스레드가 새로 생성된다.이것은 일반적으로 서버 코드에 좋지 않은 전략이다.CPU가 포화 상태가 되면 기존 작업을 완료하는 데 시간이 더 오래 걸린다.그러나 더 많은 태스크가 제출되고 더 많은 스레드가 생성되고 있기 때문에 태스크가 완료되는 데 점점 더 오랜 시간이 걸린다.CPU가 포화 상태일 때, 더 많은 스레드는 서버에 필요한 것이 아니다.

다음은 내가 추천하는 사항이다.

고정 크기 스레드 풀 실행기 사용newFixedThreadPool 또는 ThreadPoolExecutor.설정된 최대 스레드 수

Javadoc에 명시된 대로 단명 비동기 작업이 있는 경우에만 newCachedThreadPool을 사용해야 하며, 처리 시간이 더 오래 걸리는 작업을 제출하면 스레드가 너무 많이 생성된다.오래 실행되는 작업을 더 빠른 속도로 newCachedThreadPool(http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/))에 제출하면 100% CPU를 누를 수 있다.

나는 몇 가지 간단한 테스트를 하고 다음과 같은 결과를 얻는다.

1) SynchronousQueue를 사용하는 경우:

나사산이 최대 크기에 도달한 후에는 아래와 같은 예외를 제외하고 새로운 작업이 거부된다.

스레드 "main" java.util.concurrent의 예외.거절했다실행예외:태스크 java.util.concurrent.FutureTask@3fee733d는 java.util.concurrent에서 거부되었다.ThreadPoolExecutor@5acf9800[실행, 풀 크기 = 3, 활성 스레드 = 3, 대기 중인 작업 = 0, 완료된 작업 = 0]

Java.util.property.property에서.ThreadPoolExecutor$Abort정책.거부됨실행(ThreadPoolExecutor.java:2047)

2) LinkedBlockingQueue를 사용하는 경우:

스레드는 최소 크기에서 최대 크기로 늘지 않으며, 이는 스레드 풀이 최소 크기만큼 고정 크기임을 의미한다.

참조URL: https://stackoverflow.com/questions/949355/executors-newcachedthreadpool-versus-executors-newfixedthreadpool

반응형