У мене виникає проблема, коли, якщо я спробую змінити розмір 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
, обернувши наявну, яка повторно подає завдання, які не вдалося подати через зміни розміру?