크기 제한이있는 캐시 된 스레드 풀을 만들 수 없습니까?


127

캐시 가능한 스레드 풀을 생성 할 수있는 스레드 수로 제한하는 것은 불가능한 것 같습니다.

정적 Executors.newCachedThreadPool이 표준 Java 라이브러리에서 구현되는 방법은 다음과 같습니다.

 public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

따라서 해당 템플리트를 사용하여 고정 크기의 캐시 된 스레드 풀을 작성하십시오.

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronusQueue<Runable>());

이제 이것을 사용하고 3 개의 작업을 제출하면 모든 것이 정상입니다. 추가 작업을 제출하면 거부 된 실행 예외가 발생합니다.

이것을 시도 :

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runable>());

모든 스레드가 순차적으로 실행됩니다. 즉, 스레드 풀은 작업을 처리하기 위해 둘 이상의 스레드를 만들지 않습니다.

ThreadPoolExecutor의 execute 메소드에 버그가 있습니까? 아니면 이것은 의도적입니까? 아니면 다른 방법이 있습니까?

편집 : 캐시 된 스레드 풀과 똑같은 것을 원합니다 (요청시 스레드를 생성 한 다음 타임 아웃 후에 종료). 만들 수있는 스레드 수와 한 번 추가 작업을 계속 큐에 넣을 수있는 기능에 제한이 있습니다. 스레드 제한에 도달했습니다. sjlee의 답변에 따르면 이것은 불가능합니다. ThreadPoolExecutor의 execute () 메소드를 보면 실제로 불가능합니다. ThreadPoolExecutor의 서브 클래스를 작성하고 SwingWorker와 비슷한 execute ()를 재정의해야하지만 SwingWorker가 execute ()에서 수행하는 작업은 완전한 해킹입니다.


1
귀하의 질문은 무엇인가? 두 번째 코드 예제가 제목에 대한 답변이 아닙니까?
rsp

4
작업 수가 증가함에 따라 필요에 따라 스레드를 추가하는 스레드 풀이 필요하지만 최대 스레드 수를 초과하지는 않습니다. CachedThreadPool은 이미 스레드를 무제한 추가하고 미리 정의 된 크기로 멈추지 않는 것을 제외하고는 이미이 작업을 수행합니다. 예제에서 정의한 크기는 3입니다. 두 번째 예제는 스레드 1 개를 추가하지만 다른 작업이 아직 완료되지 않은 상태에서 새 작업이 도착하면 두 개를 더 추가하지 않습니다.
Matt Crinklaw-Vogt

이것을 확인하면 해결됩니다. 디버깅
isfun.blogspot.com/2012/05/…

답변:


235

ThreadPoolExecutor에는 다음과 같은 몇 가지 주요 동작이 있으며 이러한 동작으로 문제를 설명 할 수 있습니다.

작업이 제출되면

  1. 스레드 풀이 코어 크기에 도달하지 않으면 새 스레드가 작성됩니다.
  2. 코어 크기에 도달하고 유휴 스레드가 없으면 작업을 대기열에 넣습니다.
  3. 코어 크기에 도달하면 유휴 스레드가없고 큐가 가득 차면 새 스레드를 작성합니다 (최대 크기에 도달 할 때까지).
  4. 최대 크기에 도달하면 유휴 스레드가없고 큐가 가득 차면 거부 정책이 시작됩니다.

첫 번째 예에서 SynchronousQueue의 크기는 기본적으로 0입니다. 따라서 최대 크기 (3)에 도달하면 거부 정책이 시작됩니다 (# 4).

두 번째 예에서 선택한 큐는 크기가 무제한 인 LinkedBlockingQueue입니다. 그러므로, 당신은 행동 # 2에 갇히게됩니다.

동작이 거의 완전히 결정되었으므로 캐시 된 유형 또는 고정 유형으로 실제로 많은 것을 해결할 수 없습니다.

제한된 동적 스레드 풀을 사용하려면 유한 크기의 큐와 결합 된 포지티브 코어 크기 및 최대 크기를 사용해야합니다. 예를 들어

new ThreadPoolExecutor(10, // core size
    50, // max size
    10*60, // idle timeout
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(20)); // queue with a size

부록 : 이것은 상당히 오래된 답변이며 JDK가 0의 코어 크기와 관련하여 동작을 변경 한 것으로 보입니다. JDK 1.6부터 코어 크기가 0이고 풀에 스레드가 없으면 ThreadPoolExecutor는 해당 작업을 실행할 스레드 따라서 코어 크기 0은 위의 규칙에 대한 예외입니다. 감사합니다 스티브 에 대한 데려 내 관심 해당합니다.


4
allowCoreThreadTimeOut이 답변을 완벽하게 만들기 위해 방법 에 대해 몇 마디 만 써야합니다 . @ user1046052의 답변보기
hsestupin

1
좋은 답변입니다! 추가해야 할 한 가지 점 : 다른 거부 정책도 언급 할 가치가 있습니다. @brianegge의 답변보기
Jeff

1
동작 2는 ' maxThread 크기에 도달했고 유휴 스레드가 없으면 작업을 대기열에 넣습니다'라고 말합니다. ?
Zoltán

1
대기열의 크기가 의미하는 바를 자세히 설명해 주시겠습니까? 거부되기 전에 20 개의 작업 만 대기열에 넣을 수 있다는 의미입니까?
Zoltán

1
@ Zoltán 나는 이것을 얼마 전에 썼기 때문에 그 이후로 일부 행동이 바뀌었을 가능성이 있습니다 (최근 활동을 너무 면밀히 따르지 않았습니다). 아마도 이것의 가장 중요한 (그리고 다소 놀라운) 요점입니다. 코어 크기에 도달하면 TPE는 새 스레드 작성보다 큐잉을 선호합니다. 대기열 크기는 문자 그대로 TPE에 전달되는 대기열의 크기입니다. 대기열이 가득 찼지만 최대 크기에 도달하지 않으면 새 스레드가 생성됩니다 (작업을 거부하지 않음). # 3을 참조하십시오. 희망이 도움이됩니다.
sjlee

60

내가 놓친 것이 없다면, 원래 질문에 대한 해결책은 간단합니다. 다음 코드는 원본 포스터에서 설명한대로 원하는 동작을 구현합니다. 바인딩되지 않은 큐에서 작동하기 위해 최대 5 개의 스레드를 생성하고 유휴 스레드는 60 초 후에 종료됩니다.

tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>());
tp.allowCoreThreadTimeOut(true);

1
당신이 올바른지. 이 방법은 jdk 1.6에 추가되었으므로 많은 사람들이 알지 못합니다. 또한 "최소"코어 풀 크기를 가질 수 없으므로 불행합니다.
jtahlborn

4
이것에 대한 나의 유일한 걱정은 (JDK 8 문서에서) : "새로운 작업이 execute (Runnable) 메소드에 제출되고 corePoolSize 스레드보다 적은 수의 스레드가 실행되고있을 때 다른 작업자라도 요청을 처리하기 위해 새로운 스레드가 생성됩니다 스레드가 유휴 상태입니다. "
veegee

이것이 실제로 작동하지 않는다는 것을 확신하십시오. 지난번에는 위의 작업을 실제로 보았지만 5를 생성하더라도 실제로는 하나의 스레드에서만 작업을 실행하는 것으로 보았습니다. 몇 년이 지났지 만 ThreadPoolExecutor의 구현에 뛰어 들었을 때 대기열이 가득 차면 새 스레드에만 전달됩니다. 무제한 큐를 사용하면이 문제가 발생하지 않습니다. 작업을 제출하고 스레드 이름을 로그인 한 후 잠자기하여 테스트 할 수 있습니다. 모든 실행 가능 파일은 동일한 이름을 인쇄하게되고 다른 스레드에서는 실행되지 않습니다.
Matt Crinklaw-Vogt

2
작동합니다, 매트 코어 크기를 0으로 설정했기 때문에 스레드가 1 개뿐입니다. 여기서 트릭은 코어 크기를 최대 크기로 설정하는 것입니다.
T-Gergely

1
@ vegee is right-이것은 실제로 잘 작동하지 않습니다-ThreadPoolExecutor는 corePoolSize 이상일 때만 스레드를 재사용합니다. 따라서 corePoolSize가 maxPoolSize와 같으면 풀이 가득 찼을 때 스레드 캐싱의 이점 만 얻을 수 있습니다 (따라서 이것을 사용하려고하지만 일반적으로 최대 풀 크기 미만으로 유지하는 경우 스레드 시간 제한을 낮게 줄일 수도 있습니다) 값 및 캐싱이 없다는 것을 인식 - 항상 새로운 스레드)
크리스 리델

7

같은 문제가 있었다. 다른 답변으로 모든 문제를 해결할 수는 없으므로 다음을 추가하고 있습니다.

이제는 문서로 명확하게 작성되었습니다 . LinkedBlockingQueue최대 스레드 설정을 차단하지 않는 큐를 사용하면 ( ) 설정이 적용되지 않고 코어 스레드 만 사용됩니다.

그래서:

public class MyExecutor extends ThreadPoolExecutor {

    public MyExecutor() {
        super(4, 4, 5,TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        allowCoreThreadTimeOut(true);
    }

    public void setThreads(int n){
        setMaximumPoolSize(Math.max(1, n));
        setCorePoolSize(Math.max(1, n));
    }

}

이 유언 집행 인은 :

  1. 무제한 큐를 사용하므로 최대 스레드 개념이 없습니다. 이러한 큐가 executor가 일반적인 정책을 따르는 경우 코어가 아닌 추가 스레드를 대량으로 만들 수 있기 때문에 이는 좋은 일입니다.

  2. 최대 크기의 큐 Integer.MAX_VALUE. 보류중인 작업 수가 초과하면 Submit()throw RejectedExecutionException됩니다 Integer.MAX_VALUE. 먼저 메모리가 부족하다는 확신이 들지 않습니다.

  3. 4 개의 핵심 스레드가 가능합니다. 유휴 코어 스레드는 5 초 동안 유휴 상태 인 경우 자동으로 종료되므로 엄격하게 주문형 스레드 setThreads()입니다.

  4. 코어 스레드의 최소 수가 1보다 작지 않아야 submit()합니다. 그렇지 않으면 모든 작업이 거부됩니다. 코어 스레드는> = max threads 여야하므로 setThreads()최대 스레드 설정도 제한되지 않지만 대기열에 최대 스레드 설정은 쓸모가 없습니다.


나는 또한 'allowCoreThreadTimeOut'을 'true'로 설정해야한다고 생각합니다. 그렇지 않으면 스레드가 생성되면 영원히 유지합니다 : gist.github.com/ericdcobb/46b817b384f5ca9d5f5d
eric

죄송합니다. 죄송합니다. 그때 답변이 완벽합니다!
eric

6

첫 번째 예에서는 AbortPolicy이가 기본값 이므로 후속 작업이 거부 RejectedExecutionHandler됩니다. ThreadPoolExecutor에는 다음 정책이 포함되어 있으며,이 정책은 setRejectedExecutionHandler메소드 를 통해 변경할 수 있습니다 .

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

CallerRunsPolicy를 사용하여 캐시 된 스레드 풀을 원하는 것처럼 들립니다.


5

여기에 대한 답변 중 어느 것도 Apache의 HTTP 클라이언트 (3.x 버전)를 사용하여 제한된 양의 HTTP 연결을 만드는 것과 관련된 내 문제를 해결하지 못했습니다. 좋은 설정을 찾는 데 몇 시간이 걸렸으므로 다음과 같이 공유합니다.

private ExecutorService executor = new ThreadPoolExecutor(5, 10, 60L,
  TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
  Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

이것은 ThreadPoolExecutor5로 시작하고 실행에 사용하는 최대 10 개의 동시 실행 스레드를 보유합니다 CallerRunsPolicy.


이 솔루션의 문제점은 수 또는 생산자를 늘리면 백그라운드 스레드를 실행하는 스레드 수가 증가한다는 것입니다. 많은 경우에 그것은 당신이 원하는 것이 아닙니다.
Gray

3

ThreadPoolExecutor 용 Javadoc에 따라 :

corePoolSize 이상이지만 maximumPoolSize 미만의 스레드가 실행중인 경우 큐가 가득 찬 경우에만 새 스레드가 작성 됩니다 . corePoolSize와 maximumPoolSize를 동일하게 설정하여 고정 크기 스레드 풀을 만듭니다.

(Emphasis mine.)

지터의 대답은 당신이 원하는 것이지만, 다른 질문에 대답합니다. :)


2

옵션이 하나 더 있습니다. 새로운 SynchronousQueue를 사용하는 대신 다른 대기열도 사용할 수 있지만 크기가 1인지 확인해야하므로 executorservice가 새 스레드를 작성해야합니다.


나는 당신이 크기 0 (기본적으로)을 의미한다고 생각하므로 대기중인 작업이 없으며 매번 executorservice가 새 스레드를 만들도록 강제합니다.
Leonmax

2

많은 메소드 / 속성이 비공개이므로 예를 들어 addIfUnderMaximumPoolSize가 보호되어 있기 때문에 PooledExecutorService에서 서브 클래스 화하더라도 실제로 답변에 대한 답변이 실제로 질문에 대답하는 것처럼 보이지 않습니다. 다음을 수행하십시오.

class MyThreadPoolService extends ThreadPoolService {
    public void execute(Runnable run) {
        if (poolSize() == 0) {
            if (addIfUnderMaximumPoolSize(run) != null)
                return;
        }
        super.execute(run);
    }
}

내가 얻은 가장 가까운 것은 이것이지만 아주 좋은 해결책은 아닙니다.

new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) {
    public void execute(Runnable command) {
        if (getPoolSize() == 0 && getActiveCount() < getMaximumPoolSize()) {        
            super.setCorePoolSize(super.getCorePoolSize() + 1);
        }
        super.execute(command);
    }

    protected void afterExecute(Runnable r, Throwable t) {
         // nothing in the queue
         if (getQueue().isEmpty() && getPoolSize() > min) {
             setCorePoolSize(getCorePoolSize() - 1);
         }
    };
 };

PS는 위의 테스트를하지 않았습니다


2

다른 해결책이 있습니다. 이 솔루션은 원하는대로 작동한다고 생각합니다 (이 솔루션을 자랑스럽게 생각하지는 않지만).

final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    public boolean offer(Runnable o) {
        if (size() > 1)
            return false;
        return super.offer(o);
    };

    public boolean add(Runnable o) {
        if (super.offer(o))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
};

RejectedExecutionHandler handler = new RejectedExecutionHandler() {         
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        queue.add(r);
    }
};

dbThreadExecutor =
        new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, queue, handler);

2

이것이 당신이 원하는 것입니다 (거의 추측합니다). 설명을 보려면 Jonathan Feinberg 답변

Executors.newFixedThreadPool(int n)

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


4
물론 고정 스레드 풀을 사용할 수는 있지만 n 스레드를 영원히 남겨 두거나 종료를 호출 할 때까지 남겨 둡니다. 캐시 된 스레드 풀과 똑같은 것을 원합니다 (요청시 스레드를 생성 한 다음 시간 초과 후 스레드를 종료합니다).하지만 생성 할 수있는 스레드 수에 제한이 있습니다.
Matt Crinklaw-Vogt

0
  1. @ sjlee가ThreadPoolExecutor 제안한대로 사용할 수 있습니다.

    풀의 크기를 동적으로 제어 할 수 있습니다. 자세한 내용은이 질문을 살펴보십시오.

    동적 스레드 풀

    또는

  2. java 8에 도입 된 newWorkStealingPool API 를 사용할 수 있습니다 .

    public static ExecutorService newWorkStealingPool()

    사용 가능한 모든 프로세서를 대상 병렬 처리 수준으로 사용하여 작업 스틸 링 스레드 풀을 만듭니다.

기본적으로 병렬 처리 수준은 서버의 CPU 코어 수로 설정됩니다. 4 개의 코어 CPU 서버가있는 경우 스레드 풀 크기는 4입니다.이 API는 ForkJoinPool유형을 리턴 ExecutorService 하고 ForkJoinPool의 사용중인 스레드에서 작업을 도용하여 유휴 스레드의 작업 도용을 허용합니다.


0

문제는 다음과 같이 요약되었습니다.

캐시 된 스레드 풀과 똑같은 것을 원합니다 (요청시 스레드를 생성 한 다음 시간 초과 후 스레드를 종료합니다).하지만 생성 할 수있는 스레드 수와 추가 작업이 완료되면 계속 대기열에 넣을 수있는 기능이 제한됩니다. 스레드 제한.

솔루션을 가리 키기 전에 다음 솔루션이 작동하지 않는 이유를 설명하겠습니다.

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());

정의에 따라 SynchronousQueue는 요소를 보유 할 수 없으므로 한계 3에 도달하면 작업을 대기하지 않습니다.

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

ThreadPoolExecutor가 큐가 가득 찬 경우 corePoolSize를 초과하는 스레드 만 작성하므로 하나 이상의 스레드를 작성하지 않습니다. 그러나 LinkedBlockingQueue는 꽉 찼습니다.

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>());
executor.allowCoreThreadTimeOut(true);

기존 스레드가 유휴 상태 인 경우에도 ThreadPoolExecutor가 corePoolSize에 도달 할 때까지 스레드 수를 늘리기 때문에 corePoolSize에 도달 할 때까지 스레드를 재사용하지 않습니다. 이 단점을 극복 할 수 있다면 이것이 가장 쉬운 해결책입니다. "실제 Java 동시성"(p172의 각주)에 설명 된 솔루션이기도합니다.

설명 된 문제에 대한 유일한 완전한 해결책은 대기열의 offer메소드를 재정의 RejectedExecutionHandler하고이 질문에 대한 답변에서 설명한대로 a 를 작성하는 것과 같습니다 .


0

이것은 Java8 이상에서 작동합니다.

     Executor executor = new ThreadPoolExecutor(3, 3, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>()){{allowCoreThreadTimeOut(true);}};

여기서 3은 스레드 수 제한이며 5는 유휴 스레드에 대한 시간 초과입니다.

당신이 할 경우 는 직접 작동하는지 확인 , 여기에 일을 할 수있는 코드는 다음과 같습니다

public static void main(String[] args) throws InterruptedException {
    final int DESIRED_NUMBER_OF_THREADS=3; // limit of number of Threads for the task at a time
    final int DESIRED_THREAD_IDLE_DEATH_TIMEOUT=5; //any idle Thread ends if it remains idle for X seconds

    System.out.println( java.lang.Thread.activeCount() + " threads");
    Executor executor = new ThreadPoolExecutor(DESIRED_NUMBER_OF_THREADS, DESIRED_NUMBER_OF_THREADS, DESIRED_THREAD_IDLE_DEATH_TIMEOUT, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>()) {{allowCoreThreadTimeOut(true);}};

    System.out.println(java.lang.Thread.activeCount() + " threads");

    for (int i = 0; i < 5; i++) {
        final int fi = i;
        executor.execute(() -> waitsout("starting hard thread computation " + fi, "hard thread computation done " + fi,2000));
    }
    System.out.println("If this is UP, it works");

    while (true) {
        System.out.println(
                java.lang.Thread.activeCount() + " threads");
        Thread.sleep(700);
    }

}

static void waitsout(String pre, String post, int timeout) {
    try {
        System.out.println(pre);
        Thread.sleep(timeout);
        System.out.println(post);
    } catch (Exception e) {
    }
}

나를 위해 위의 코드 출력은

1 threads
1 threads
If this is UP, it works
starting hard thread computation 0
4 threads
starting hard thread computation 2
starting hard thread computation 1
4 threads
4 threads
hard thread computation done 2
hard thread computation done 0
hard thread computation done 1
starting hard thread computation 3
starting hard thread computation 4
4 threads
4 threads
4 threads
hard thread computation done 3
hard thread computation done 4
4 threads
4 threads
4 threads
4 threads
3 threads
3 threads
3 threads
1 threads
1 threads
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.