Je rencontre un problème où si j'essaie de redimensionner ThreadPoolExecutor
la taille du pool principal d'un à un nombre différent après la création du pool, puis par intermittence, certaines tâches sont rejetées avec un RejectedExecutionException
même si je ne soumets jamais plus de queueSize + maxPoolSize
nombre de tâches.
Le problème que j'essaye de résoudre est d'étendre ThreadPoolExecutor
qui redimensionne ses threads principaux en fonction des exécutions en attente se trouvant dans la file d'attente du pool de threads. J'en ai besoin parce que par défaut, un ThreadPoolExecutor
n'en créera un nouveau Thread
que si la file d'attente est pleine.
Voici un petit programme Pure Java 8 autonome qui illustre le problème.
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();
}
}
Pourquoi le pool devrait-il jamais lancer une RejectedExecutionException si je ne soumets jamais plus que queueCapacity + maxThreads. Je ne modifie jamais le nombre maximal de threads, donc, selon la définition de ThreadPoolExecutor, il devrait accueillir la tâche dans un thread ou dans la file d'attente.
Bien sûr, si je ne redimensionne jamais le pool, le pool de threads ne rejette aucune soumission. C'est également difficile à déboguer, car l'ajout de retards dans les soumissions fait disparaître le problème.
Des conseils sur la façon de corriger l'exception RejectedExecutionException?
ThreadPoolExecutor
est très probablement une mauvaise idée, et n'auriez-vous pas besoin de changer le code existant dans ce cas également? Il serait préférable de fournir un exemple de la façon dont votre code réel accède à l'exécuteur. Je serais surpris s'il utilisait de nombreuses méthodes spécifiques ThreadPoolExecutor
(c'est- à -dire pas dans ExecutorService
).
ExecutorService
en encapsulant une existante, qui soumet à nouveau les tâches dont la soumission a échoué en raison du redimensionnement?