Sự khác biệt chính xác giữa kích thước hồ bơi lõi và kích thước hồ bơi tối đa khi chúng ta nói về điều ThreadPoolExecutor
gì?
Nó có thể được giải thích với sự giúp đỡ của một ví dụ?
Sự khác biệt chính xác giữa kích thước hồ bơi lõi và kích thước hồ bơi tối đa khi chúng ta nói về điều ThreadPoolExecutor
gì?
Nó có thể được giải thích với sự giúp đỡ của một ví dụ?
Câu trả lời:
Lấy ví dụ này. Kích thước nhóm chủ đề bắt đầu là 1, kích thước nhóm lõi là 5, kích thước nhóm tối đa là 10 và hàng đợi là 100.
Khi có yêu cầu, các luồng sẽ được tạo tối đa 5 và sau đó các nhiệm vụ sẽ được thêm vào hàng đợi cho đến khi đạt 100. Khi hàng đợi đầy, các luồng mới sẽ được tạo lên đến
maxPoolSize
. Khi tất cả các luồng được sử dụng và hàng đợi đã đầy đủ các tác vụ sẽ bị từ chối. Khi hàng đợi giảm, số luồng hoạt động cũng vậy.
allowCoreThreadTimeOut(boolean)
cho phép các luồng lõi bị giết sau thời gian nhàn rỗi nhất định. Đặt giá trị này thành true và setting core threads
= max threads
cho phép nhóm luồng mở rộng từ 0 đến max threads
.
NẾU đang chạy các luồng> corePoolSize & <maxPoolSize , thì hãy tạo một Luồng mới nếu Tổng hàng đợi tác vụ đã đầy và sắp có hàng mới.
Mẫu doc: (Nếu có nhiều hơn corePoolSize nhưng ít hơn maximumPoolSize đề chạy, một chủ đề mới sẽ được tạo ra chỉ khi hàng đợi đã đầy.)
Bây giờ, hãy lấy một ví dụ đơn giản,
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
Ở đây, 5 là corePoolSize - nghĩa là Jvm sẽ tạo luồng mới cho tác vụ mới cho 5 tác vụ đầu tiên. và các nhiệm vụ khác sẽ được thêm vào hàng đợi cho đến khi hàng đợi đầy (50 nhiệm vụ).
10 là maxPoolSize - JVM có thể tạo tối đa 10 luồng. Có nghĩa là nếu đã có 5 tác vụ / luồng đang chạy và hàng đợi đã đầy với 50 tác vụ đang chờ xử lý và nếu có thêm một yêu cầu / tác vụ mới đến trong hàng đợi thì JVM sẽ tạo luồng mới lên đến 10 (tổng số chủ đề = 5 trước đó + 5 mới) ;
new ArrayBlockingQueue (50) = là tổng kích thước hàng đợi - nó có thể xếp 50 nhiệm vụ trong đó.
khi tất cả 10 luồng đang chạy và nếu nhiệm vụ mới đến thì tác vụ mới đó sẽ bị từ chối.
Quy tắc tạo Chủ đề nội bộ của SUN:
Nếu số luồng ít hơn corePoolSize, hãy tạo một luồng mới để chạy một tác vụ mới.
Nếu số luồng bằng (hoặc lớn hơn) thì corePoolSize, hãy đưa tác vụ vào hàng đợi.
Nếu hàng đợi đã đầy và số luồng ít hơn maxPoolSize, hãy tạo một luồng mới để chạy các tác vụ trong đó.
Nếu hàng đợi đầy và số luồng lớn hơn hoặc bằng maxPoolSize, hãy từ chối tác vụ.
Hy vọng, đây là HelpFul .. và vui lòng sửa cho tôi nếu tôi sai ...
Từ tài liệu :
Khi một tác vụ mới được gửi trong phương thức thực thi (java.lang.Runnable) và ít hơn các luồng corePoolSize đang chạy, một luồng mới sẽ được tạo để xử lý yêu cầu, ngay cả khi các luồng worker khác không hoạt động. Nếu có nhiều luồng corePoolSize nhưng ít hơn luồng MaximumPoolSize đang chạy, một luồng mới sẽ chỉ được tạo khi hàng đợi đầy.
Hơn nữa:
Bằng cách đặt corePoolSize và MaximumPoolSize giống nhau, bạn tạo một nhóm luồng có kích thước cố định. Bằng cách đặt MaximumPoolSize thành một giá trị không bị ràng buộc về cơ bản, chẳng hạn như Integer.MAX_VALUE, bạn cho phép nhóm chứa một số tác vụ đồng thời tùy ý. Thông thường nhất, kích thước lõi và kích thước nhóm tối đa chỉ được đặt khi xây dựng, nhưng chúng cũng có thể được thay đổi động bằng cách sử dụng setCorePoolSize (int) và setMaximumPoolSize (int).
Nếu bạn quyết định tạo một ThreadPoolExecutor
thủ công thay vì sử dụng Executors
lớp nhà máy, bạn sẽ cần tạo và định cấu hình một lớp bằng cách sử dụng một trong các hàm tạo của nó. Hàm tạo mở rộng nhất của lớp này là:
public ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAlive,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
);
Như bạn thấy, bạn có thể cấu hình:
Giới hạn số lượng tác vụ đồng thời đang được thực thi, định kích thước nhóm luồng của bạn, thể hiện một lợi ích to lớn cho ứng dụng của bạn và môi trường thực thi của nó về khả năng dự đoán và ổn định: việc tạo luồng không bị ràng buộc cuối cùng sẽ làm cạn kiệt tài nguyên thời gian chạy và ứng dụng của bạn có thể gặp phải hậu quả , các vấn đề về hiệu suất nghiêm trọng có thể dẫn đến sự mất ổn định của ứng dụng.
Đó là giải pháp cho một phần của vấn đề: bạn đang giới hạn số lượng nhiệm vụ đang được thực thi nhưng không giới hạn số lượng công việc có thể được gửi và xếp hàng để thực hiện sau này. Ứng dụng sẽ gặp phải tình trạng thiếu tài nguyên sau đó, nhưng cuối cùng sẽ gặp phải điều đó nếu tỷ lệ gửi luôn cao hơn tốc độ thực thi.
Giải pháp cho vấn đề này là: Cung cấp một hàng đợi chặn để người thực thi giữ các tác vụ đang chờ. Trong trường hợp hàng đợi lấp đầy, nhiệm vụ đã nộp sẽ bị "từ chối". Các RejectedExecutionHandler
được gọi khi gửi nhiệm vụ bị từ chối, và lý do tại sao động từ từ chối đã được trích dẫn trong mục trước. Bạn có thể triển khai chính sách từ chối của riêng mình hoặc sử dụng một trong các chính sách tích hợp được cung cấp bởi khuôn khổ.
Các chính sách từ chối mặc định có trình thực thi ném a RejectedExecutionException
. Tuy nhiên, các chính sách tích hợp khác cho phép bạn:
Quy tắc của kích thước nhóm ThreadPoolExecutor
Các quy tắc về kích thước của một ThreadPoolExecutor's
hồ bơi thường không được hiểu rõ, bởi vì nó không hoạt động theo cách bạn nghĩ nó phải làm hoặc theo cách bạn muốn.
Lấy ví dụ này. Kích thước nhóm chủ đề bắt đầu là 1, kích thước nhóm lõi là 5, kích thước nhóm tối đa là 10 và hàng đợi là 100.
Cách của Sun: khi các yêu cầu đến trong luồng sẽ được tạo tối đa 5, sau đó các nhiệm vụ sẽ được thêm vào hàng đợi cho đến khi nó đạt đến 100. Khi hàng đợi đầy, các luồng mới sẽ được tạo lên đến maxPoolSize
. Khi tất cả các luồng được sử dụng và hàng đợi đã đầy đủ các tác vụ sẽ bị từ chối. Khi hàng đợi giảm số lượng chủ đề hoạt động.
Cách người dùng dự đoán: khi các yêu cầu đến trong chuỗi sẽ được tạo tối đa 10, sau đó các nhiệm vụ sẽ được thêm vào hàng đợi cho đến khi đạt 100 tại thời điểm đó chúng bị từ chối. Số lượng chủ đề sẽ đổi tên ở mức tối đa cho đến khi hàng đợi trống. Khi hàng đợi trống, các chủ đề sẽ chết cho đến khi còn corePoolSize
lại.
Sự khác biệt là người dùng muốn bắt đầu tăng kích thước hồ bơi sớm hơn và muốn hàng đợi nhỏ hơn, trong đó phương pháp Sun muốn giữ kích thước hồ bơi nhỏ và chỉ tăng nó khi tải trở nên nhiều.
Dưới đây là các quy tắc của Sun để tạo luồng theo các thuật ngữ đơn giản:
corePoolSize
, hãy tạo một luồng mới để chạy một tác vụ mới.corePoolSize
, hãy đặt nhiệm vụ vào hàng đợi.maxPoolSize
, hãy tạo một luồng mới để chạy các tác vụ trong đó.maxPoolSize
, hãy từ chối tác vụ. Điều dài và ngắn của nó là các luồng mới chỉ được tạo khi hàng đợi đầy, vì vậy nếu bạn đang sử dụng một hàng đợi không bị ràng buộc thì số luồng sẽ không vượt quá corePoolSize
.Để có lời giải thích đầy đủ hơn, hãy lấy nó từ miệng ngựa: ThreadPoolExecutor
tài liệu API.
Có một bài đăng thực sự tốt trên diễn đàn nói với bạn về cách thức ThreadPoolExecutor
hoạt động với các ví dụ mã: http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0
Thông tin thêm: http://forums.sun.com/thread.jspa?threadID=5224557&tstart=450
Bạn có thể tìm thấy định nghĩa của các thuật ngữ corepoolsize và maxpoolsize trong javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
Liên kết trên có câu trả lời cho câu hỏi của bạn. Tuy nhiên, chỉ để làm cho nó rõ ràng. Ứng dụng sẽ tiếp tục tạo các luồng cho đến khi nó đạt đến corePoolSize. Tôi nghĩ ý tưởng ở đây là nhiều luồng này phải đủ để xử lý luồng tác vụ. Nếu một tác vụ mới đến sau khi các luồng corePoolSize được tạo, các tác vụ sẽ được xếp hàng đợi. Khi hàng đợi đầy, trình thực thi sẽ bắt đầu tạo các luồng mới. Đó là loại cân bằng. Về cơ bản, ý nghĩa của nó là lượng tác vụ đổ vào nhiều hơn khả năng xử lý. Vì vậy, Executor sẽ bắt đầu tạo lại các chủ đề mới cho đến khi nó đạt đến số lượng Tối đa. Một lần nữa, một luồng mới sẽ được tạo nếu và chỉ khi hàng đợi đầy.
Giải thích tốt trong blog này :
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;
}
}
}
}
}
Đầu ra:
---- 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
Từ cuốn sách Những điều cơ bản về tính tương đồng trong Java :
CorePoolSize : ThreadPoolExecutor có thuộc tính corePoolSize xác định số lượng luồng nó sẽ bắt đầu cho đến khi các luồng mới chỉ được bắt đầu khi hàng đợi đầy
MaximumPoolSize : Thuộc tính này xác định tối đa bao nhiêu luồng được bắt đầu. Bạn có thể đặt giá trị này thành Số nguyên. MAX_VALUE để không có ranh giới trên
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);
}
Hiểu được hành vi nội bộ của thời ThreadPoolExecutor
điểm một nhiệm vụ mới được đệ trình đã giúp tôi hiểu được sự khác biệt corePoolSize
và như thế nào maximumPoolSize
.
Để cho:
N
là số luồng trong nhóm getPoolSize()
,. Chủ đề hoạt động + chủ đề nhàn rỗi.T
là số lượng nhiệm vụ được gửi cho người thực thi / nhóm.C
là kích thước hồ bơi cốt lõi , getCorePoolSize()
. Có thể tạo tối đa bao nhiêu luồng cho mỗi nhóm cho các tác vụ đến trước khi các tác vụ mới chuyển đến hàng đợi .M
là kích thước hồ bơi tối đa , getMaximumPoolSize()
. Số lượng luồng tối đa mà nhóm có thể phân bổ.Các hành vi ThreadPoolExecutor
trong Java khi một nhiệm vụ mới được gửi:
N <= C
, các luồng nhàn rỗi không được giao nhiệm vụ mới đến, thay vào đó một luồng mới được tạo.N > C
và nếu có các luồng nhàn rỗi thì nhiệm vụ mới sẽ được giao ở đó.N > C
và nếu KHÔNG có luồng nhàn rỗi, các tác vụ mới được đưa vào hàng đợi. KHÔNG CÓ THREAD MỚI ĐƯỢC TẠO Ở ĐÂY.M
. Nếu M
đạt được, chúng tôi từ chối nhiệm vụ. Điều quan trọng không phải ở đây là chúng tôi không tạo luồng mới cho đến khi hàng đợi đầy!Nguồn:
corePoolSize = 0
và maximumPoolSize = 10
với dung lượng hàng đợi là 50
.Điều này sẽ dẫn đến một chuỗi hoạt động duy nhất trong nhóm cho đến khi hàng đợi có 50 mục trong đó.
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
và maximumPoolSize = 10
với dung lượng hàng đợi là 50
.Điều này sẽ dẫn đến 10 luồng hoạt động trong nhóm. Khi hàng đợi có 50 mục trong đó, nhiệm vụ sẽ bị từ chối.
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`.