ThreadPoolExecutor
풀을 만든 후의 코어 풀 크기를 다른 숫자 로 크기 조정하려고하면 간헐적으로 여러 작업을 RejectedExecutionException
제출하지 않아도 일부 작업이 거부되는 문제가 발생 queueSize + maxPoolSize
합니다.
내가 해결하려고하는 문제는 ThreadPoolExecutor
스레드 풀의 대기열에 앉아 보류중인 실행을 기반으로 핵심 스레드의 크기를 조정하는 것입니다. 기본적으로 a ThreadPoolExecutor
가 Thread
대기열이 가득 찬 경우에만 새 항목을 작성 하기 때문에 이것이 필요합니다 .
다음은 문제를 보여주는 작은 독립형 순수 Java 8 프로그램입니다.
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolResizeTest {
public static void main(String[] args) throws Exception {
// increase the number of iterations if unable to reproduce
// for me 100 iterations have been enough
int numberOfExecutions = 100;
for (int i = 1; i <= numberOfExecutions; i++) {
executeOnce();
}
}
private static void executeOnce() throws Exception {
int minThreads = 1;
int maxThreads = 5;
int queueCapacity = 10;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
minThreads, maxThreads,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(queueCapacity),
new ThreadPoolExecutor.AbortPolicy()
);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> resizeThreadPool(pool, minThreads, maxThreads),
0, 10, TimeUnit.MILLISECONDS);
CompletableFuture<Void> taskBlocker = new CompletableFuture<>();
try {
int totalTasksToSubmit = queueCapacity + maxThreads;
for (int i = 1; i <= totalTasksToSubmit; i++) {
// following line sometimes throws a RejectedExecutionException
pool.submit(() -> {
// block the thread and prevent it from completing the task
taskBlocker.join();
});
// Thread.sleep(10); //enabling even a small sleep makes the problem go away
}
} finally {
taskBlocker.complete(null);
scheduler.shutdown();
pool.shutdown();
}
}
/**
* Resize the thread pool if the number of pending tasks are non-zero.
*/
private static void resizeThreadPool(ThreadPoolExecutor pool, int minThreads, int maxThreads) {
int pendingExecutions = pool.getQueue().size();
int approximateRunningExecutions = pool.getActiveCount();
/*
* New core thread count should be the sum of pending and currently executing tasks
* with an upper bound of maxThreads and a lower bound of minThreads.
*/
int newThreadCount = min(maxThreads, max(minThreads, pendingExecutions + approximateRunningExecutions));
pool.setCorePoolSize(newThreadCount);
pool.prestartAllCoreThreads();
}
}
queueCapacity + maxThreads 이상을 제출하지 않으면 풀에서 RejectedExecutionException을 발생시켜야하는 이유는 무엇입니까? ThreadPoolExecutor의 정의에 의해 최대 스레드를 변경하지는 않습니다. 스레드 또는 대기열의 작업을 수용해야합니다.
물론 풀의 크기를 조정하지 않으면 스레드 풀이 제출을 거부하지 않습니다. 제출에 지연을 추가하면 문제가 해결되므로 디버깅하기도 어렵습니다.
RejectedExecutionException을 해결하는 방법에 대한 조언이 있습니까?
ThreadPoolExecutor
은 아마도 나쁜 생각 일 것입니다.이 경우에도 기존 코드를 변경할 필요가 없습니까? 실제 코드가 실행 프로그램에 액세스하는 방법에 대한 몇 가지 예를 제공하는 것이 가장 좋습니다. 그것이 많은 ThreadPoolExecutor
(즉,에없는 ExecutorService
) 많은 방법을 사용하면 놀랍습니다 .
ExecutorService
크기 조정으로 인해 제출에 실패한 작업을 다시 제출하는 기존 작업을 래핑하여 구현 한 이유는 무엇 입니까?