ThreadPoolExecutor
プールの作成後にのコアプールサイズを別の数値に変更しようとすると、タスクの数をRejectedExecutionException
超えて送信しなくても、一部のタスクが断続的に拒否されるという問題が発生していますqueueSize + maxPoolSize
。
私が解決しようとしている問題はThreadPoolExecutor
、スレッドプールのキューにある保留中の実行に基づいてコアスレッドのサイズを変更して拡張することです。デフォルトでThreadPoolExecutor
はThread
、キューがいっぱいの場合にのみ新しいが作成されるため、これが必要です。
これは、問題を示す小さな自己完結型のPure 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
サイズ変更が原因で送信に失敗したタスクを再送信する既存の実装をラップして、独自の実装を提供してみませんか?