Я сталкиваюсь с проблемой, при которой, если я пытаюсь изменить размер 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();
}
}
Зачем пулу когда-либо выдавать RejectedExecutionException, если я никогда не отправлю больше, чем queueCapacity + maxThreads. Я никогда не меняю максимальное количество потоков, поэтому по определению ThreadPoolExecutor он должен либо разместить задачу в потоке, либо в очереди.
Конечно, если я никогда не изменяю размер пула, то пул потоков никогда не отклоняет любые представления. Это также трудно отладить, поскольку добавление каких-либо задержек в представлениях устраняет проблему.
Любые указатели о том, как исправить RejectedExecutionException?
ThreadPoolExecutorскорее всего, плохая идея, и не нужно ли вам в этом случае изменять существующий код? Было бы лучше, если бы вы предоставили пример того, как ваш реальный код обращается к исполнителю. Я был бы удивлен, если бы он использовал много методов, специфичных для ThreadPoolExecutor(то есть не в ExecutorService).
ExecutorServiceпутем обертывания существующей, которая повторно отправляет задачи, которые не удалось отправить из-за изменения размера?