Was genau ist der Unterschied zwischen der Größe des Kernpools und der maximalen Poolgröße, wenn wir darüber sprechen ThreadPoolExecutor
?
Kann es anhand eines Beispiels erklärt werden?
Was genau ist der Unterschied zwischen der Größe des Kernpools und der maximalen Poolgröße, wenn wir darüber sprechen ThreadPoolExecutor
?
Kann es anhand eines Beispiels erklärt werden?
Antworten:
Aus diesem Blog-Beitrag :
Nehmen Sie dieses Beispiel. Die Größe des Start-Thread-Pools beträgt 1, die Größe des Kernpools 5, die maximale Poolgröße 10 und die Warteschlange 100.
Wenn Anfragen eingehen, werden Threads bis zu 5 erstellt, und dann werden Aufgaben zur Warteschlange hinzugefügt, bis sie 100 erreicht. Wenn die Warteschlange voll ist, werden neue Threads bis zu erstellt
maxPoolSize
. Sobald alle Threads verwendet werden und die Warteschlange voll ist, werden Aufgaben abgelehnt. Wenn sich die Warteschlange verringert, verringert sich auch die Anzahl der aktiven Threads.
allowCoreThreadTimeOut(boolean)
, mit der Core-Threads nach einer bestimmten Leerlaufzeit beendet werden können. Wenn Sie dies auf true setzen und core threads
= setzen, max threads
kann der Thread-Pool zwischen 0 und 0 skalieren max threads
.
IF laufenden Threads> corePoolSize & <MaxPoolSize , dann einen neuen Thread erstellen , wenn Gesamt - Task - Queue voll und neue ist ankommt.
Formular doc: (Wenn mehr als corePoolSize, aber weniger als maximalPoolSize- Threads ausgeführt werden, wird nur dann ein neuer Thread erstellt, wenn die Warteschlange voll ist.)
Nehmen Sie nun ein einfaches Beispiel:
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
Hier ist 5 die corePoolSize - bedeutet, dass Jvm für die ersten 5 Aufgaben einen neuen Thread für eine neue Aufgabe erstellt. und andere Aufgaben werden der Warteschlange hinzugefügt, bis die Warteschlange voll ist (50 Aufgaben).
10 ist die maxPoolSize - JVM kann maximal 10 Threads erstellen. Bedeutet, wenn bereits 5 Aufgaben / Thread ausgeführt werden und die Warteschlange mit 50 ausstehenden Aufgaben voll ist und eine weitere neue Anforderung / Aufgabe in der Warteschlange eintrifft, erstellt JVM einen neuen Thread mit bis zu 10 (Gesamtzahl der Threads = vorherige 5 + neue 5) ;;
new ArrayBlockingQueue (50) = ist eine Gesamtgröße der Warteschlange - es können 50 Aufgaben in die Warteschlange gestellt werden.
Sobald alle 10 Threads ausgeführt werden und eine neue Aufgabe eintrifft, wird diese neue Aufgabe abgelehnt.
Regeln zum internen Erstellen von Threads durch SUN:
Wenn die Anzahl der Threads geringer als corePoolSize ist, erstellen Sie einen neuen Thread, um eine neue Aufgabe auszuführen.
Wenn die Anzahl der Threads gleich (oder größer als) der corePoolSize ist, stellen Sie die Aufgabe in die Warteschlange.
Wenn die Warteschlange voll ist und die Anzahl der Threads geringer als maxPoolSize ist, erstellen Sie einen neuen Thread, in dem Aufgaben ausgeführt werden sollen.
Wenn die Warteschlange voll ist und die Anzahl der Threads größer oder gleich maxPoolSize ist, lehnen Sie die Aufgabe ab.
Hoffe, das ist HelpFul .. und bitte korrigiere mich, wenn ich falsch liege ...
Aus dem Dokument :
Wenn eine neue Aufgabe in der Methode execute (java.lang.Runnable) übergeben wird und weniger als corePoolSize-Threads ausgeführt werden, wird ein neuer Thread erstellt, um die Anforderung zu verarbeiten, selbst wenn andere Arbeitsthreads inaktiv sind. Wenn mehr als corePoolSize, aber weniger als maximalPoolSize-Threads ausgeführt werden, wird ein neuer Thread nur erstellt, wenn die Warteschlange voll ist.
Außerdem:
Indem Sie corePoolSize und maximumPoolSize gleich einstellen, erstellen Sie einen Thread-Pool mit fester Größe. Indem Sie maximumPoolSize auf einen im Wesentlichen unbegrenzten Wert wie Integer.MAX_VALUE setzen, können Sie dem Pool erlauben, eine beliebige Anzahl von gleichzeitigen Aufgaben aufzunehmen. In der Regel werden die Kern- und Maximalpoolgrößen nur bei der Erstellung festgelegt, sie können jedoch auch dynamisch mit setCorePoolSize (int) und setMaximumPoolSize (int) geändert werden.
Wenn Sie eine ThreadPoolExecutor
manuell erstellen möchten, anstatt die Executors
Factory-Klasse zu verwenden, müssen Sie eine mit einem ihrer Konstruktoren erstellen und konfigurieren. Der umfangreichste Konstruktor dieser Klasse ist:
public ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAlive,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
);
Wie Sie sehen können, können Sie Folgendes konfigurieren:
Die Begrenzung der Anzahl der gleichzeitig ausgeführten Aufgaben und die Größe Ihres Thread-Pools stellt einen großen Vorteil für Ihre Anwendung und ihre Ausführungsumgebung in Bezug auf Vorhersagbarkeit und Stabilität dar: Eine unbegrenzte Thread-Erstellung erschöpft möglicherweise die Laufzeitressourcen und Ihre Anwendung kann dies zur Folge haben , schwerwiegende Leistungsprobleme, die sogar zu Instabilität der Anwendung führen können.
Dies ist eine Lösung für nur einen Teil des Problems: Sie begrenzen die Anzahl der ausgeführten Aufgaben, aber nicht die Anzahl der Jobs, die für die spätere Ausführung gesendet und in die Warteschlange gestellt werden können. Bei der Anwendung tritt später eine Ressourcenknappheit auf, die jedoch möglicherweise auftritt, wenn die Übermittlungsrate konstant über der Ausführungsrate liegt.
Die Lösung für dieses Problem lautet: Bereitstellen einer Blockierungswarteschlange für den Executor, um die wartenden Aufgaben zu speichern. Wenn die Warteschlange voll ist, wird die übermittelte Aufgabe "abgelehnt". Das RejectedExecutionHandler
wird aufgerufen, wenn eine Aufgabenübermittlung abgelehnt wird, und deshalb wurde das abgelehnte Verb im vorherigen Element zitiert. Sie können Ihre eigene Ablehnungsrichtlinie implementieren oder eine der vom Framework bereitgestellten integrierten Richtlinien verwenden.
Die Standard-Ablehnungsrichtlinien lassen den Executor a auslösen RejectedExecutionException
. Mit anderen integrierten Richtlinien können Sie jedoch:
Regeln einer ThreadPoolExecutor-Poolgröße
Die Regeln für die Größe von a ThreadPoolExecutor's
Pools werden im Allgemeinen falsch verstanden, da sie nicht so funktionieren, wie Sie es sich vorstellen oder wie Sie es möchten.
Nehmen Sie dieses Beispiel. Die Größe des Start-Thread-Pools beträgt 1, die Größe des Kernpools 5, die maximale Poolgröße 10 und die Warteschlange 100.
So geht's: Wenn Anfragen in Threads eingehen, werden bis zu 5 erstellt, dann werden Aufgaben zur Warteschlange hinzugefügt, bis sie 100 erreicht. Wenn die Warteschlange voll ist, werden neue Threads bis zu 5 erstellt maxPoolSize
. Sobald alle Threads verwendet werden und die Warteschlange voll ist, werden Aufgaben abgelehnt. Wenn sich die Warteschlange verringert, verringert sich auch die Anzahl der aktiven Threads.
Vom Benutzer erwartete Art und Weise: Wenn Anforderungen in Threads eingehen, werden bis zu 10 erstellt, und Aufgaben werden der Warteschlange hinzugefügt, bis sie 100 erreicht und an diesem Punkt abgelehnt werden. Die Anzahl der Threads wird maximal umbenannt, bis die Warteschlange leer ist. Wenn die Warteschlange leer ist, sterben die Threads ab, bis sie corePoolSize
übrig sind.
Der Unterschied besteht darin, dass die Benutzer die Poolgröße früher erhöhen und die Warteschlange kleiner machen möchten, während die Sun-Methode die Poolgröße klein halten und erst erhöhen möchte, wenn die Last zu groß wird.
Hier sind die Regeln von Sun für die Thread-Erstellung in einfachen Worten:
corePoolSize
, erstellen Sie einen neuen Thread, um eine neue Aufgabe auszuführen.corePoolSize
, stellen Sie die Aufgabe in die Warteschlange.maxPoolSize
, erstellen Sie einen neuen Thread, in dem Aufgaben ausgeführt werden.maxPoolSize
, lehnen Sie die Aufgabe ab. Das lange und das kurze daran ist, dass neue Threads nur erstellt werden, wenn die Warteschlange voll ist. Wenn Sie also eine unbegrenzte Warteschlange verwenden, wird die Anzahl der Threads nicht überschritten corePoolSize
.Eine ausführlichere Erklärung finden Sie im Pferdemund: ThreadPoolExecutor
API-Dokumentation.
Es gibt einen wirklich guten Forumsbeitrag, der Ihnen zeigt, wie das ThreadPoolExecutor
mit Codebeispielen funktioniert: http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0
Weitere Informationen: http://forums.sun.com/thread.jspa?threadID=5224557&tstart=450
Die Definition der Begriffe Corepoolsize und Maxpoolsize finden Sie im Javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
Der obige Link hat die Antwort auf Ihre Frage. Nur um es klar zu machen. Die Anwendung erstellt weiterhin Threads, bis sie die corePoolSize erreicht. Ich denke, die Idee hier ist, dass diese vielen Themen ausreichen sollten, um den Zufluss von Aufgaben zu bewältigen. Wenn eine neue Aufgabe kommt, nachdem die corePoolSize-Threads erstellt wurden, werden die Aufgaben in die Warteschlange gestellt. Sobald die Warteschlange voll ist, erstellt der Executor neue Threads. Es ist eine Art Ausgleich. Im Wesentlichen bedeutet dies, dass der Zufluss von Aufgaben mehr ist als die Verarbeitungskapazität. Executor erstellt also erneut neue Threads, bis die maximale Anzahl von Threads erreicht ist. Auch hier werden neue Threads erstellt, wenn die Warteschlange voll ist.
Gute Erklärung in diesem 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;
}
}
}
}
}
Ausgabe :
---- 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
Aus dem Buch Java Concurency Essentials :
CorePoolSize : Der ThreadPoolExecutor verfügt über ein Attribut corePoolSize, das bestimmt, wie viele Threads gestartet werden, bis neue Threads erst gestartet werden, wenn die Warteschlange voll ist
MaximumPoolSize : Dieses Attribut bestimmt, wie viele Threads maximal gestartet werden. Sie können dies auf Integer setzen. MAX_VALUE, um keine obere Grenze zu haben
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);
}
Das Verständnis des internen Verhaltens ThreadPoolExecutor
beim Einreichen einer neuen Aufgabe hat mir geholfen zu verstehen, wie corePoolSize
und wie sich diese maximumPoolSize
unterscheiden.
Lassen:
N
die Anzahl der Threads im Pool sein , getPoolSize()
. Aktive Threads + Leerlauf-Threads.T
Die Anzahl der Aufgaben, die an den Executor / Pool gesendet werden.C
die Kernpoolgröße sein , getCorePoolSize()
. Wie viele Threads können höchstens pro Pool für die eingehenden Aufgaben erstellt werden, bevor neue Aufgaben in die Warteschlange gestellt werden .M
die maximale Poolgröße sein , getMaximumPoolSize()
. Maximale Anzahl von Threads, die der Pool zuweisen kann.Verhalten von ThreadPoolExecutor
in Java beim Senden einer neuen Aufgabe:
N <= C
den inaktiven Threads wird nicht die neue eingehende Aufgabe zugewiesen, sondern ein neuer Thread wird erstellt.N > C
und wenn es freie Threads gibt, wird dort eine neue Aufgabe zugewiesen.N > C
und wenn KEINE Leerlauf-Threads vorhanden sind, werden neue Aufgaben in die Warteschlange gestellt. HIER KEIN NEUES GEWINDE ERSTELLT.M
. Wenn M
erreicht, lehnen wir die Aufgaben ab. Was hier nicht wichtig ist, ist, dass wir keine neuen Threads erstellen, bis die Warteschlange voll ist!Quellen:
corePoolSize = 0
und maximumPoolSize = 10
mit einer Warteschlangenkapazität von 50
.Dies führt zu einem einzelnen aktiven Thread im Pool, bis die Warteschlange 50 Elemente enthält.
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
und maximumPoolSize = 10
mit einer Warteschlangenkapazität von 50
.Dies führt zu 10 aktiven Threads im Pool. Wenn die Warteschlange 50 Elemente enthält, werden Aufgaben abgelehnt.
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`.