Detailed use of the Java thread pool ThreadPoolExecutor class

Keywords: Java less

1. Executors Create Thread Pool

2. ThreadPoolExecutor class

3. ThreadPoolExecutor class extension

 

1. Executors Create Thread Pool

Creating a thread pool in Java is easy, just call the corresponding convenient methods in Executors, such as Executors.newFixedThreadPool(), Executors.newSingleThreadExecutor(), Executors.newCachedThreadPool().These methods are convenient, but they also have limitations, such as OOM, thread exhaustion.

Applets have no problem using these shortcuts. For programs that need to run long on the server side, creating a thread pool should be done directly using ThreadPoolExecutor.The creation of these convenient methods is also achieved through ThreadPoolExecutor.

2. ThreadPoolExecutor class

1. Thread pool working order

The order in which the thread pool works is: corePoolSize -> Task Queue -> maximumPoolSize -> Rejection Policy

2. ThreadPoolExecutor constructor

The quick way to create a thread pool in Executors is actually to invoke the construction method of ThreadPoolExecutor, while Executors.newScheduledThreadPool() is internally used by ScheduledThreadPoolExecutor.The ThreadPoolExecutor constructor parameter list is as follows:

1 public ThreadPoolExecutor(int corePoolSize,                     //Number of thread pool core threads
2                           int maximumPoolSize,                  //Maximum Threads in Thread Pool
3                           long keepAliveTime,                   //Exceed corePooleSize The lifetime of idle threads
4                           TimeUnit unit,                        //Idle Thread Lifetime Units
5                           BlockingQueue<Runnable> workQueue,    //Queue of tasks
6                           ThreadFactory threadFactory,          //Thread Factory for New Threads
7                           RejectedExecutionHandler handler)     //Rejection Policy

The more problematic parameters are corePoolSize, maximumPoolSize, workQueue, and handler:

  • Incorrect corePoolSize and maximumPoolSize settings can affect efficiency and even exhaust threads
  • Inappropriate workQueue settings can easily lead to OOM
  • Improper handler settings cause an exception to be thrown when submitting a task

3. WorQueue Task Queue

Task queues are generally divided into direct submission queues, bounded task queues, unsolved task queues, priority task queues.

  • Direct task queue: Set to SynchronousQueue queue.SynchronousQueue is a special BlockingQueue that has no capacity and will block every insert, requiring another delete to wake up; otherwise, each delete will wait for the corresponding insert.
 1 public class SynchronousQueueTest {
 2 
 3     private static ExecutorService pool;
 4     
 5     public static void main(String[] args) {
 6         
 7         //Number of core threads set to 1,Set maximum number of threads to 2,Task Queue SynchronousQueue,The rejection policy is AbortPolicy,Throw an exception directly
 8         pool = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
 9         for(int i = 0;i < 3;i++){
10             pool.execute(new ThreadTask());
11         }
12     }
13 }
14 
15 class ThreadTask implements Runnable{
16     
17     @Override
18     public void run() {
19         System.out.println(Thread.currentThread().getName());
20     }
21 }

The results are as follows:

pool-1-thread-1
pool-1-thread-2
Exception in thread "main" java.util.concurrent.RejectedExecutionException: 
Task com.aisino.threadPool.ThreadTask@2f0e140b rejected from java.util.concurrent.ThreadPoolExecutor@7440e464[Running,
pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at com.aisino.threadPool.SynchronousQueueTest.main(SynchronousQueueTest.java:18)

From the execution results, when the task queue is SynchronousQueue and the number of threads created is greater than maximumPoolSize, the direct execution of the rejection policy throws an exception.

When using a SynchronousQueue queue, submitted tasks are not saved and are always immediately committed for execution.If the number of threads used to execute a task is less than maximumPoolSize, an attempt is made to create a new thread, and if the maximum set by maximumPoolSize is reached, the corresponding rejection policy is executed according to the handler set.Therefore, when using a SynchronousQueue queue, the task is not cached but executed immediately, in which case an accurate evaluation of the concurrency of the program is required to set the appropriate number of maximumPoolSize, otherwise the rejection policy can be easily executed.

  • Bounded Task Queue: Can be implemented using ArrayBlockingQueue as follows:
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10), 
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

When using the ArrayBlockingQueue bounded task queue, if new tasks need to be executed, the thread pool creates new threads until the number of threads created reaches corePoolSize, after which new tasks are added to the waiting queue.If the wait queue is full, which exceeds the capacity initialized by ArrayBlockingQueue, continue creating threads until the number of threads reaches the maximum number set by maximumPoolSize and if it is larger than maximumPoolSize, a deny policy is executed.In this case, the upper limit on the number of threads is directly related to the state of the bounded task queue. If the bounded task queue has a large initial number or is not overloaded, the number of threads will remain at or below corePoolSize; otherwise, when the task queue is full, the maximum number of threads will be maximumPoolSizeUpper limit.

  • Unbounded Task Queue: Can be implemented using LinkedBlockingQueue as follows:
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

With an unbounded task queue, the task queue of the thread pool can add new tasks without restriction, and the maximum number of threads created by the thread pool is corePoolSize.In this case, the maximumPoolSize parameter is invalid, even if many unexecuted tasks are cached in the task queue, the number of threads will not increase when the number of threads in the thread pool reaches corePoolSize.If new tasks follow, they enter the queue and wait.When using this task queue mode, be aware of the coordinated control between task submission and processing, otherwise the tasks in the queue will grow because they cannot be processed in time until they run out of resources.

  • Priority Queue: Implemented through PriorityBlockingQueue as follows:
 1 public class PriorityBlockingQueueTest {
 2 
 3     private static ExecutorService pool;
 4     public static void main(String[] args) {
 5         
 6         //Use Priority Queue
 7         pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
 8         for(int i = 0;i < 10;i++){
 9             pool.execute(new PriorityThreadTask(i));
10         }
11     }
12     
13 }
14 
15 class PriorityThreadTask implements Runnable, Comparable<PriorityThreadTask>{
16 
17     private int priority;
18     
19     public int getPriority(){
20         return priority;
21     }
22     public void setPriority(int priority){
23         this.priority = priority;
24     }
25     
26     public PriorityThreadTask(){}
27     
28     public PriorityThreadTask(int priority){
29         this.priority = priority;
30     }
31 
32     @Override
33     public void run() {
34         
35         try{
36             //Block Thread,Cache subsequent tasks
37             Thread.sleep(1000);
38             
39             System.out.println("priority:" + this.priority + ", ThreadName:" + Thread.currentThread().getName());
40         }catch(InterruptedException e){
41             e.printStackTrace();
42         }
43     }
44 
45     //Compare Current Object with Other Objects,Return if current priority is high-1,Return 1 if current priority is low,The smaller the value, the higher the priority
46     @Override
47     public int compareTo(PriorityThreadTask o) {
48         return this.priority > o.priority ? -1 : 1;
49     }
50 }

The results are as follows:

priority:0, ThreadName:pool-1-thread-1
priority:19, ThreadName:pool-1-thread-1
priority:18, ThreadName:pool-1-thread-1
priority:17, ThreadName:pool-1-thread-1
priority:16, ThreadName:pool-1-thread-1
priority:15, ThreadName:pool-1-thread-1
priority:14, ThreadName:pool-1-thread-1
priority:13, ThreadName:pool-1-thread-1
priority:12, ThreadName:pool-1-thread-1
priority:11, ThreadName:pool-1-thread-1
priority:10, ThreadName:pool-1-thread-1
priority:9, ThreadName:pool-1-thread-1
priority:8, ThreadName:pool-1-thread-1
priority:7, ThreadName:pool-1-thread-1
priority:6, ThreadName:pool-1-thread-1
priority:5, ThreadName:pool-1-thread-1
priority:4, ThreadName:pool-1-thread-1
priority:3, ThreadName:pool-1-thread-1
priority:2, ThreadName:pool-1-thread-1
priority:1, ThreadName:pool-1-thread-1

From the execution results, you can see that, except for the first task, which directly creates the thread execution, all other tasks are placed in the Priority BlockingQueue priority queue, rearranged by priority, and the number of threads in the thread pool has been corePoolSize, in this case corePoolSize is 1, that is, the number of threads has been1.

PriorityBlockingQueue is a special boundless queue in which no matter how many tasks are added, the thread pool will not create more threads than corePoolSize.Other queues generally process tasks according to FIFO rules, while PriorityBlockingQueue queues can customize rules to execute in turn according to the priority order of tasks.

4. handler Rejection Policy

When creating a thread pool, task queues always choose to create bounded task queues in order to prevent resources from being exhausted.In Create Bounded Task Queue mode, when the task queue is full and the number of threads created by the thread pool reaches the maximum number of threads, you need to specify the RejectedExecutionHandler parameter of ThreadPoolExecutor to handle the situation where the thread pool is "overloaded".ThreadPoolExecutor's own rejection policy is as follows:

  • AbortPolicy policy: This policy throws exceptions directly, preventing the system from working properly
  • DiscardPolicy policy: This policy silently discards unprocessable tasks and does nothing.Allow task loss in business scenarios when using this policy
  • DiscardOldestPolicy policy: This policy discards the oldest task in the task queue, that is, the first task added to the task queue that is about to be executed, and attempts to submit the task again (repeat the process)
  • CallerRunsPolicy policy: If the maximum number of threads in the thread pool is reached, this policy places tasks in the task queue running in the caller thread

The above built-in rejection policies both implement the RejectedExecutionHandler interface, or they can extend the RejectedExecutionHandler interface themselves to define their own rejection policies.The sample code is as follows:

 1 /**
 2  * Custom Rejection Policy
 3  */
 4 public class CustomRejectedExecutionHandlerTest {
 5 
 6     private static ExecutorService pool;
 7 
 8     public static void main(String[] args) {
 9 
10         //Custom Rejection Policy
11         pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,
12                 new ArrayBlockingQueue<>(5),
13                 Executors.defaultThreadFactory(),
14                 new RejectedExecutionHandler() {
15                     @Override
16                     public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
17                         System.out.println(r.toString() + " Rejection Policy Executed");
18                     }
19                 });
20 
21         for (int i = 0; i < 10; i++) {
22             pool.execute(new CustomRejectedExecutionHandlerThreadTask());
23         }
24     }
25 }
26 
27 class CustomRejectedExecutionHandlerThreadTask implements Runnable {
28 
29     @Override
30     public void run() {
31         try {
32             //Block Thread,Cache subsequent task machines
33             Thread.sleep(1000);
34             System.out.println("Thread Name:" + Thread.currentThread().getName());
35         } catch (InterruptedException e) {
36             e.printStackTrace();
37         }
38     }
39 }

The results are as follows:

The rejection policy was executed by com.aisino.threadPool.CustomRejectedExecutionHandlerThreadTask@3cd1a2f1
Rejection policy was implemented at com.aisino.threadPool.CustomRejectedExecutionHandlerThreadTask@2f0e140b
Rejection policy was implemented at com.aisino.threadPool.CustomRejectedExecutionHandlerThreadTask@7440e464
Thread name: pool-1-thread-2
Thread name: pool-1-thread-1
Thread name: pool-1-thread-2
Thread name: pool-1-thread-1
Thread name: pool-1-thread-2
Thread name: pool-1-thread-1
Thread name: pool-1-thread-2

The result of execution shows that due to the addition of hibernation blockade to a task, it takes time to execute the task, resulting in a certain number of tasks being discarded to execute a custom rejection policy.

5. ThreadFactory custom thread creation

Threads in the thread pool are created through the ThreadFactory thread factory in ThreadPoolExecutor.ThreadFactory can be customized to make special settings (naming, prioritizing, and so on) for threads in the thread pool.The sample code is as follows:

 1 public class CustomThreadFactoryTest {
 2     
 3     private static ExecutorService pool;
 4 
 5     public static void main(String[] args) {
 6         //Custom Thread Factory
 7         pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5), new ThreadFactory() {
 8             @Override
 9             public Thread newThread(Runnable r) {
10                 System.out.println("Create Threads:" + r.hashCode());
11                 //Thread Name
12                 Thread th = new Thread(r, "threadPool-" + r.hashCode());
13                 return th;
14             }
15         }, new ThreadPoolExecutor.CallerRunsPolicy());
16         
17         for(int i = 0;i < 10;i++){
18             pool.execute(new CustomThreadFactoryThreadTask());
19         }
20     }
21 }
22 
23 class CustomThreadFactoryThreadTask implements Runnable{
24     
25     @Override
26     public void run(){
27         //Name of output execution thread
28         System.out.println("Thread Name:" + Thread.currentThread().getName());
29     }
30 }

The results are as follows:

Create Threads: 1259475182
Create Thread: 1300109446
Create Thread: 1020371697
Thread name: threadPool-1259475182
Thread name: threadPool-1300109446
Thread name: threadPool-1259475182
Create Thread: 789451787
Thread name: threadPool-1020371697
Thread name: threadPool-1259475182
Thread name: threadPool-1300109446
Thread name: threadPool-1259475182
Thread name: threadPool-1020371697
Thread name: threadPool-789451787
Thread name: threadPool-1300109446

The results of the execution show that each thread is created with a recorded output and named.

6. Correctly construct thread pool

int poolSize = Runtime.getRuntime().availableProcessors() * 2;
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
executorService = new ThreadPoolExecutor(poolSize, poolSize,
    0, TimeUnit.SECONDS,
            queue,
            policy);

 

3. ThreadPoolExecutor class extension

The ThreadPoolExecutor class extension mainly surrounds three interfaces, beforeExecute(), afterExecute(), and terminated().

  • beforeExecute: Executes before a task runs in a thread pool
  • afterExecute: Executes when a task in the thread pool has finished running
  • terminated: Executed after the thread pool exits

These three interfaces allow you to monitor the start and end times of each task, or other functions.The sample code is as follows:

 1 public class ThreadPoolExecutorExtensionTest {
 2     
 3     private static ExecutorService pool;
 4 
 5     public static void main(String[] args) {
 6         
 7         //Custom Threads,Rename Threads
 8         pool = new ThreadPoolExecutor(1, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5), new ThreadFactory() {
 9             @Override
10             public Thread newThread(Runnable r) {
11                 System.out.println("Create Threads:" + r.hashCode());
12                 Thread th = new Thread(r, "ThreadPool-" + r.hashCode());
13                 return th;
14             }
15         }, new ThreadPoolExecutor.CallerRunsPolicy()){
16             
17             protected void beforeExecute(Thread t,Runnable r) {
18                 System.out.println("The name of the task to be executed:"+ ((ThreadPoolExecutorExtensionThreadTask)r).getTaskName());
19             }
20 
21             protected void afterExecute(Runnable r,Throwable t) {
22                 System.out.println("Completed task name:"+((ThreadPoolExecutorExtensionThreadTask)r).getTaskName());
23             }
24 
25             protected void terminated() {
26                 System.out.println("Thread pool exit");
27             }
28         };
29         
30         for(int i = 0;i < 10;i++){
31             pool.execute(new ThreadPoolExecutorExtensionThreadTask("Task-" + i));
32         }
33         pool.shutdown();
34     }
35 }
36 
37 class ThreadPoolExecutorExtensionThreadTask implements Runnable{
38     
39     private String taskName;
40     
41     public String getTaskName(){
42         return taskName;
43     }
44     
45     public void setTaskName(String taskName){
46         this.taskName = taskName;
47     }
48     
49     public ThreadPoolExecutorExtensionThreadTask(){}
50     
51     public ThreadPoolExecutorExtensionThreadTask(String taskName){
52         this.taskName = taskName;
53     }
54 
55     @Override
56     public void run() {
57         //Output Task Name and Corresponding Execution Thread Name
58         System.out.println("Task Name:" + this.taskName + ", Execution Thread Name:" + Thread.currentThread().getName());
59     }
60 }

The results are as follows:

Create Threads: 1259475182
Create Thread: 1300109446
Create Thread: 1020371697
Name of the task to be executed: Task-0
Create Thread: 789451787
Task Name: Task-0, Execution Thread Name: ThreadPool-1259475182
Name of the task to be executed: Task-6
Task Name: Task-9, Execution Thread Name: main
Task Name: Task-0
Name of the task to be executed: Task-7
Name of the task to be executed: Task-8
Task Name: Task-6, Execution Thread Name: ThreadPool-1300109446
Task Name: Task-8, Execution Thread Name: ThreadPool-789451787
Task Name: Task-8
Name of the task to be executed: Task-1
Task Name: Task-7, Execution Thread Name: ThreadPool-1020371697
Task Name: Task-7
Task Name: Task-1, Execution Thread Name: ThreadPool-1259475182
Task Name: Task-1
Name of the task to be executed: Task-2
Task Name: Task-2, Execution Thread Name: ThreadPool-789451787
Task Name: Task-2
Task Name: Task-6
Name of the task to be executed: Task-5
Task Name: Task-5, Execution Thread Name: ThreadPool-789451787
Task Name: Task-5
Name of the task to be executed: Task-4
Name of the task to be executed: Task-3
Task Name: Task-4, Execution Thread Name: ThreadPool-1259475182
Task Name: Task-4
Task Name: Task-3, Execution Thread Name: ThreadPool-1020371697
Task Name: Task-3
Thread pool exit

The results of the execution show that by implementing beforeExecute(), afterExecute(), and terminated(), the state of threads in the thread pool can be monitored and print information is output before and after the thread execution.In addition, the shutdown() method can safely close the thread pool, which will no longer accept subsequent added tasks when called by the thread pool.However, the thread pool does not exit immediately; it does not exit until all the tasks added to the thread pool have been processed.

Posted by omprakash on Thu, 26 Sep 2019 22:41:57 -0700