我遇到了一个问题,如果ThreadPoolExecutor
在创建池之后尝试将核心池的大小调整为其他数字,那么RejectedExecutionException
即使我提交queueSize + maxPoolSize
的任务数从来没有超过,我也会间歇性地拒绝某些任务。
我试图解决的问题是扩展ThreadPoolExecutor
,以基于坐在线程池队列中的挂起执行来调整其核心线程的大小。我需要这样做,因为默认情况下,只有在队列已满时,a 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();
}
}
如果我提交的队列容量+ maxThreads不多,为什么池会抛出RejectedExecutionException。我从不更改最大线程数,因此按照ThreadPoolExecutor的定义,它应该将任务容纳在线程中或放入队列中。
当然,如果我从不调整池的大小,那么线程池将永远不会拒绝任何提交。这也很难调试,因为在提交中添加任何形式的延迟都会使问题消失。
关于如何修复RejectedExecutionException的任何指针?
ThreadPoolExecutor
很可能不是一个好主意,在这种情况下您是否也不需要更改现有代码?最好提供一些示例,说明您的实际代码如何访问执行程序。如果它使用了许多特定于ThreadPoolExecutor
(即不是ExecutorService
)的方法,我会感到惊讶。
ExecutorService
通过包装一个现有的实现来提供自己的实现,该实现将重新提交由于调整大小而导致提交失败的任务?