Quelle est exactement la différence entre la taille du pool de base et la taille maximale du pool lorsque nous parlons de ThreadPoolExecutor
?
Peut-on l'expliquer à l'aide d'un exemple?
Quelle est exactement la différence entre la taille du pool de base et la taille maximale du pool lorsque nous parlons de ThreadPoolExecutor
?
Peut-on l'expliquer à l'aide d'un exemple?
Réponses:
De cet article de blog :
Prenons cet exemple. La taille du pool de threads de départ est 1, la taille du pool principal est de 5, la taille maximale du pool est de 10 et la file d'attente est de 100.
Au fur et à mesure que les demandes arrivent, les threads seront créés jusqu'à 5, puis les tâches seront ajoutées à la file d'attente jusqu'à ce qu'elle atteigne 100. Lorsque la file d'attente est pleine, de nouveaux threads seront créés jusqu'à
maxPoolSize
. Une fois que tous les threads sont utilisés et que la file d'attente est pleine, les tâches seront rejetées. À mesure que la file d'attente se réduit, le nombre de threads actifs diminue également.
allowCoreThreadTimeOut(boolean)
qui permet de tuer les threads principaux après un temps d'inactivité donné. Définir ceci sur true et définir core threads
= max threads
permet au pool de threads de s'échelonner entre 0 et max threads
.
Si vous exécutez des threads> corePoolSize & <maxPoolSize , créez un nouveau thread si la file d'attente de tâches Total est pleine et qu'une nouvelle arrive.
Form doc: (S'il y a plus de threads corePoolSize mais moins que maximumPoolSize en cours d'exécution, un nouveau thread sera créé uniquement si la file d'attente est pleine.)
Maintenant, prenons un exemple simple,
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
Ici, 5 est le corePoolSize - signifie que Jvm créera un nouveau thread pour une nouvelle tâche pour les 5 premières tâches. et d'autres tâches seront ajoutées à la file d'attente jusqu'à ce que la file d'attente soit pleine (50 tâches).
10 est le maxPoolSize - JVM peut créer un maximum de 10 threads. Signifie que s'il y a déjà 5 tâches / threads en cours d'exécution et que la file d'attente est pleine de 50 tâches en attente et si une nouvelle demande / tâche supplémentaire arrive dans la file d'attente, JVM créera un nouveau thread jusqu'à 10 (nombre total de threads = 5 précédents + nouveau 5) ;
new ArrayBlockingQueue (50) = est une taille totale de file d'attente - il peut y mettre 50 tâches en file d'attente.
une fois que les 10 threads sont en cours d'exécution et si une nouvelle tâche arrive, cette nouvelle tâche sera rejetée.
Règles de création de threads en interne par SUN:
Si le nombre de threads est inférieur à corePoolSize, créez un nouveau Thread pour exécuter une nouvelle tâche.
Si le nombre de threads est égal (ou supérieur) à corePoolSize, placez la tâche dans la file d'attente.
Si la file d'attente est pleine et que le nombre de threads est inférieur à maxPoolSize, créez un nouveau thread pour exécuter les tâches.
Si la file d'attente est pleine et que le nombre de threads est supérieur ou égal à maxPoolSize, rejetez la tâche.
J'espère que c'est utile ... et s'il vous plaît, corrigez-moi si je me trompe ...
Du doc :
Lorsqu'une nouvelle tâche est soumise dans method execute (java.lang.Runnable) et que moins de threads corePoolSize sont en cours d'exécution, un nouveau thread est créé pour gérer la demande, même si d'autres threads de travail sont inactifs. S'il y a plus de threads corePoolSize mais moins que maximumPoolSize en cours d'exécution, un nouveau thread sera créé uniquement si la file d'attente est pleine.
En outre:
En définissant de la même manière corePoolSize et maximumPoolSize, vous créez un pool de threads de taille fixe. En définissant maximumPoolSize sur une valeur essentiellement illimitée telle que Integer.MAX_VALUE, vous autorisez le pool à accueillir un nombre arbitraire de tâches simultanées. Le plus généralement, les tailles de pool de base et maximum sont définies uniquement lors de la construction, mais elles peuvent également être modifiées dynamiquement à l'aide de setCorePoolSize (int) et setMaximumPoolSize (int).
Si vous décidez de créer un ThreadPoolExecutor
manuellement au lieu d'utiliser la Executors
classe de fabrique, vous devrez en créer et en configurer un à l'aide de l'un de ses constructeurs. Le constructeur le plus étendu de cette classe est:
public ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAlive,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
);
Comme vous pouvez le voir, vous pouvez configurer:
Limiter le nombre de tâches simultanées en cours d'exécution, dimensionner votre pool de threads, représente un énorme avantage pour votre application et son environnement d'exécution en termes de prévisibilité et de stabilité: une création de thread illimitée finira par épuiser les ressources d'exécution et votre application pourrait en subir les conséquences. , de graves problèmes de performances pouvant même entraîner une instabilité de l'application.
C'est une solution à une seule partie du problème: vous limitez le nombre de tâches en cours d'exécution mais ne limitez pas le nombre de travaux qui peuvent être soumis et mis en file d'attente pour une exécution ultérieure. L'application connaîtra une pénurie de ressources plus tard, mais elle finira par en souffrir si le taux de soumission dépasse systématiquement le taux d'exécution.
La solution à ce problème est: Fournir une file d'attente de blocage à l'exécuteur pour contenir les tâches en attente. Dans le cas où la file d'attente se remplit, la tâche soumise sera "rejetée". Le RejectedExecutionHandler
est appelé lorsqu'une soumission de tâche est rejetée, et c'est pourquoi le verbe rejeté a été cité dans l'élément précédent. Vous pouvez mettre en œuvre votre propre politique de rejet ou utiliser l'une des politiques intégrées fournies par l'infrastructure.
Les politiques de rejet par défaut obligent l'exécuteur à lancer un RejectedExecutionException
. Cependant, d'autres stratégies intégrées vous permettent:
Règles d'une taille de pool ThreadPoolExecutor
Les règles relatives à la taille d'une ThreadPoolExecutor's
piscine sont généralement mal comprises, car cela ne fonctionne pas comme vous le pensez ou comme vous le souhaitez.
Prenons cet exemple. La taille du pool de threads de départ est 1, la taille du pool principal est de 5, la taille maximale du pool est de 10 et la file d'attente est de 100.
La manière de Sun: au fur et à mesure que les requêtes arrivent, les threads seront créés jusqu'à 5, puis les tâches seront ajoutées à la file d'attente jusqu'à ce qu'elle atteigne 100. Lorsque la file d'attente est pleine, de nouveaux threads seront créés jusqu'à maxPoolSize
. Une fois que tous les threads sont utilisés et que la file d'attente est pleine, les tâches seront rejetées. À mesure que la file d'attente diminue, le nombre de threads actifs diminue également.
Manière prévue par l'utilisateur: au fur et à mesure que les demandes arrivent, les threads seront créés jusqu'à 10, puis les tâches seront ajoutées à la file d'attente jusqu'à ce qu'elle atteigne 100, point auquel elles sont rejetées. Le nombre de threads sera renommé au maximum jusqu'à ce que la file d'attente soit vide. Lorsque la file d'attente est vide, les threads mourront jusqu'à ce qu'il en corePoolSize
reste.
La différence est que les utilisateurs veulent commencer à augmenter la taille du pool plus tôt et veulent que la file d'attente soit plus petite, alors que la méthode Sun veut garder la taille du pool petite et ne l'augmenter que lorsque la charge devient trop importante.
Voici les règles de Sun pour la création de threads en termes simples:
corePoolSize
, créez un nouveau thread pour exécuter une nouvelle tâche.corePoolSize
, placez la tâche dans la file d'attente.maxPoolSize
, créez un nouveau thread pour exécuter les tâches.maxPoolSize
, rejetez la tâche. Le long et court est que de nouveaux threads ne sont créés que lorsque la file d'attente se remplit, donc si vous utilisez une file d'attente illimitée, le nombre de threads ne dépassera pas corePoolSize
.Pour une explication plus complète, obtenez-le de la bouche du cheval: ThreadPoolExecutor
documentation de l'API.
Il y a un très bon message sur le forum qui vous explique comment cela ThreadPoolExecutor
fonctionne avec des exemples de code: http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0
Plus d'infos: http://forums.sun.com/thread.jspa?threadID=5224557&tstart=450
Vous pouvez trouver la définition des termes corepoolsize et maxpoolsize dans le javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
Le lien ci-dessus a la réponse à votre question. Cependant, juste pour que ce soit clair. L'application continuera à créer des threads jusqu'à ce qu'elle atteigne corePoolSize. Je pense que l'idée ici est que ces nombreux threads devraient être suffisants pour gérer l'afflux de tâches. Si une nouvelle tâche survient après la création des threads corePoolSize, les tâches seront mises en file d'attente. Une fois la file d'attente pleine, l'exécuteur commencera à créer de nouveaux threads. C'est une sorte d'équilibre. Cela signifie essentiellement que l'afflux de tâches est supérieur à la capacité de traitement. Ainsi, Executor recommencera à créer de nouveaux threads jusqu'à ce qu'il atteigne le nombre maximum de threads. Encore une fois, un nouveau thread sera créé si et seulement si la file d'attente est pleine.
Bonne explication dans ce blog:
public class ThreadPoolExecutorExample {
public static void main (String[] args) {
createAndRunPoolForQueue(new ArrayBlockingQueue<Runnable>(3), "Bounded");
createAndRunPoolForQueue(new LinkedBlockingDeque<>(), "Unbounded");
createAndRunPoolForQueue(new SynchronousQueue<Runnable>(), "Direct hand-off");
}
private static void createAndRunPoolForQueue (BlockingQueue<Runnable> queue,
String msg) {
System.out.println("---- " + msg + " queue instance = " +
queue.getClass()+ " -------------");
ThreadPoolExecutor e = new ThreadPoolExecutor(2, 5, Long.MAX_VALUE,
TimeUnit.NANOSECONDS, queue);
for (int i = 0; i < 10; i++) {
try {
e.execute(new Task());
} catch (RejectedExecutionException ex) {
System.out.println("Task rejected = " + (i + 1));
}
printStatus(i + 1, e);
}
e.shutdownNow();
System.out.println("--------------------\n");
}
private static void printStatus (int taskSubmitted, ThreadPoolExecutor e) {
StringBuilder s = new StringBuilder();
s.append("poolSize = ")
.append(e.getPoolSize())
.append(", corePoolSize = ")
.append(e.getCorePoolSize())
.append(", queueSize = ")
.append(e.getQueue()
.size())
.append(", queueRemainingCapacity = ")
.append(e.getQueue()
.remainingCapacity())
.append(", maximumPoolSize = ")
.append(e.getMaximumPoolSize())
.append(", totalTasksSubmitted = ")
.append(taskSubmitted);
System.out.println(s.toString());
}
private static class Task implements Runnable {
@Override
public void run () {
while (true) {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
break;
}
}
}
}
}
Production :
---- Bounded queue instance = class java.util.concurrent.ArrayBlockingQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueCapacity = 1, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 3, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 4, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------
---- Unbounded queue instance = class java.util.concurrent.LinkedBlockingDeque -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2147483646, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueRemainingCapacity = 2147483645, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 2147483644, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 2, corePoolSize = 2, queueSize = 4, queueRemainingCapacity = 2147483643, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 2, corePoolSize = 2, queueSize = 5, queueRemainingCapacity = 2147483642, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 2, corePoolSize = 2, queueSize = 6, queueRemainingCapacity = 2147483641, maximumPoolSize = 5, totalTasksSubmitted = 8
poolSize = 2, corePoolSize = 2, queueSize = 7, queueRemainingCapacity = 2147483640, maximumPoolSize = 5, totalTasksSubmitted = 9
poolSize = 2, corePoolSize = 2, queueSize = 8, queueRemainingCapacity = 2147483639, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------
---- Direct hand-off queue instance = class java.util.concurrent.SynchronousQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 3, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 4, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
Task rejected = 6
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
Task rejected = 7
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
Task rejected = 8
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------
Process finished with exit code 0
Extrait du livre Java concurency essentials :
CorePoolSize : Le ThreadPoolExecutor a un attribut corePoolSize qui détermine le nombre de threads qu'il démarrera jusqu'à ce que les nouveaux threads ne soient démarrés que lorsque la file d'attente est pleine
MaximumPoolSize : cet attribut détermine combien de threads sont démarrés au maximum. Vous pouvez le définir sur Integer. MAX_VALUE pour ne pas avoir de limite supérieure
java.util.concurrent.ThreadPoolExecutor
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
Comprendre le comportement interne du ThreadPoolExecutor
lorsqu'une nouvelle tâche est soumise m'a aidé à comprendre comment corePoolSize
et maximumPoolSize
différer.
Laisser:
N
le nombre de fils dans la piscine, getPoolSize()
. Threads actifs + threads inactifs.T
être la quantité de tâches soumises à l'exécuteur / pool.C
la taille du pool de base, getCorePoolSize()
. Combien de threads au maximum peuvent être créés par pool pour les tâches entrantes avant que les nouvelles tâches ne soient placées dans la file d'attente .M
la taille de la piscine maximale, getMaximumPoolSize()
. Nombre maximum de threads que le pool peut allouer.Comportements de ThreadPoolExecutor
en Java lorsqu'une nouvelle tâche est soumise:
N <= C
, les threads inactifs ne reçoivent pas la nouvelle tâche entrante, mais un nouveau thread est créé.N > C
et s'il y a des threads inactifs, une nouvelle tâche y est affectée.N > C
et s'il n'y a AUCUN thread inactif, de nouvelles tâches sont placées dans la file d'attente. AUCUN NOUVEAU FIL CRÉÉ ICI.M
. Si M
est atteint, nous rejetons les tâches. Ce qui est important de ne pas ici, c'est que nous ne créons pas de nouveaux threads tant que la file d'attente n'est pas pleine!Sources:
corePoolSize = 0
et maximumPoolSize = 10
avec une capacité de file d'attente de50
.Cela se traduira par un seul thread actif dans le pool jusqu'à ce que la file d'attente contienne 50 éléments.
executor.execute(task #1):
before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
[task #1 immediately queued and kicked in b/c the very first thread is created when `workerCountOf(recheck) == 0`]
execute(task #2):
before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
[task #2 not starting before #1 is done]
... executed a few tasks...
execute(task #19)
before task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 17, completed tasks = 0]
after task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 18, completed tasks = 0]
...
execute(task #51)
before task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 50, completed tasks = 0]
after task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 2, active threads = 2, queued tasks = 50, completed tasks = 0]
Queue is full.
A new thread was created as the queue was full.
corePoolSize = 10
et maximumPoolSize = 10
avec une capacité de file d'attente de50
.Cela entraînera 10 threads actifs dans le pool. Lorsque la file d'attente contient 50 éléments, les tâches sont rejetées.
execute(task #1)
before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
execute(task #2)
before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
execute(task #3)
before task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
after task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
... executed a few tasks...
execute(task #11)
before task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 0]
after task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 1, completed tasks = 0]
... executed a few tasks...
execute(task #51)
before task #51 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 50, completed tasks = 0]
Task was rejected as we have reached `maximumPoolSize`.