Quickly master Java thread pool technology

Posted by foid025 on Sat, 08 Jan 2022 06:22:33 +0100

In fact, the concept of thread pool is not so profound. It can be simply understood as that multiple idle threads are stored in a container. When a new task needs to be executed, the idle threads are taken out of the container and returned to the container after the task is executed.

The reason for using thread pool technology is mainly because the cost of creating a new thread is relatively high, and the bottom layer of the program needs to interact with the operating system. When a large number of threads with a very short lifetime need to be created in the program, threads need to be created and destroyed frequently. The resource consumption of the system is likely to be greater than that of the business processing itself, which puts the cart before the horse (because the ultimate purpose of using multithreading is to improve the business processing capacity). In order to solve this problem as much as possible, we need to reduce the frequency of thread creation and destruction, and we need to use thread pool.

Java's thread pool implementation technology is actually very simple. In real enterprise development, 99% of the cases, you will not allow yourself to code and implement a custom thread pool. Instead, you should stand on the shoulders of giants and call the API methods officially provided by Java. The thread pool API method officially provided by Java is very simple to learn and use. Let me introduce it in detail below.


1, Create a default thread pool using Executors

We can use the two static methods provided in Executors to create thread pools, as follows:

Static method name explain
static ExecutorService newCachedThreadPool() Create a default thread pool. The maximum number of threads in the thread pool is the maximum value of int
static newFixedThreadPool(int nThreads) Create a thread pool that specifies the maximum number of threads

Code implementation:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {

        //Create a default thread pool. The maximum number of threads in the thread pool is the maximum value of int
        //ExecutorService executorService = Executors.newCachedThreadPool();

        //Create a thread pool with a maximum number of threads of 5
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;

        //Number of threads in the print thread pool
        System.out.println(pool.getPoolSize()); //The number of threads in the current thread pool is 0

        //Submit a task to the thread pool
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + " Yes");
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + " Yes");
        });

        System.out.println(pool.getPoolSize()); //The number of threads in the current thread pool is 2

        //Close destroy thread pool
        executorService.shutdown();
    }
}

As can be seen from the above code, the default thread pool officially provided by Java does not create an idle thread when it is started. When we submit a task to the thread pool, the thread pool will start a thread to execute the task. After the task is executed, the thread will not be destroyed, but returned to the thread pool in an idle state, waiting for the execution of subsequent new tasks.


2, Creating a custom thread pool using ThreadPoolExecutor

The construction method parameters for creating ThreadPoolExecutor thread pool are as follows:

Parameter 1: number of core threads
Parameter 2: maximum number of threads
Parameter 3: maximum idle thread lifetime
Parameter 4: unit of survival time (minutes, seconds, milliseconds...)
Parameter 5: task queue
Parameter 6: create a thread factory (just use the default factory: Executors.defaultThreadFactory())
Parameter 7: task rejection policy

Task rejection policy explain
ThreadPoolExecutor.AbortPolicy Redundant tasks are discarded and a RejectedExecutionException exception is thrown (default policy).
ThreadPoolExecutor.DiscardPolicy Redundant tasks are discarded, but no exceptions are thrown. This is not recommended.
ThreadPoolExecutor.DiscardOldestPolicy Discard the longest waiting task in the queue, and then add the current task to the queue.
ThreadPoolExecutor.CallerRunsPolicy Call the run() method of the task to bypass the thread pool and execute directly.

The following is a code demonstration. Here, only the code with task rejection policies of AbortPolicy and CallerRunsPolicy is demonstrated, because they are commonly used.


1 use ThreadPoolExecutor Abortpolicy task rejection policy

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo01 {

    public static void main(String[] args) {
        //The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task queue is 1, and the maximum lifetime of idle threads is 20 seconds
        ThreadPoolExecutor threadPoolExecutor 
            = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                                     new ArrayBlockingQueue<>(1) ,
                                     Executors.defaultThreadFactory() ,
                                     new ThreadPoolExecutor.AbortPolicy()) ;

        //Currently, 5 tasks are submitted, and the thread pool can process up to 4 tasks,
        //RejectedExecutionException exception will be thrown when we use AbortPolicy as the task rejection policy
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "----> Performed the task");
            });
        }
        
        //Close destroy thread pool
        threadPoolExecutor.shutdown();
    }
}

/*
You can wrap the following code with try catch to handle exceptions
threadPoolExecutor.submit(() -> {
    System.out.println(Thread.currentThread().getName() + "----> Performed task "");
});
*/

2. Use ThreadPoolExecutor Callerrunspolicy task rejection policy

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo02 {

    public static void main(String[] args) {
        //The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task queue is 1, and the maximum lifetime of idle threads is 20 seconds
        ThreadPoolExecutor threadPoolExecutor 
            = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                                     new ArrayBlockingQueue<>(1) ,
                                     Executors.defaultThreadFactory() ,
                                     new ThreadPoolExecutor.CallerRunsPolicy()) ;

        //Currently, 5 tasks are submitted, and the thread pool can process up to 4 tasks
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "----> Performed the task");
            });
        }
        
        //Close destroy thread pool
        threadPoolExecutor.shutdown();
    }
}

/*
The console output results are as follows:
pool-1-thread-1----> Performed the task
pool-1-thread-3----> Performed the task
pool-1-thread-2----> Performed the task
pool-1-thread-1----> Performed the task
main----> Performed the task

Through the output results of the console, it is found that:
The fifth task is not executed by the thread in the thread pool, but bypasses the thread pool, calls the run method and executes in the main thread.
*/

So far, the thread pool technology officially provided by Java has been introduced. The above is just a demonstration of simple code, which can be transformed according to specific business needs in practical work. In addition, in practice, the thread pool will not be manually coded, closed or destroyed. The life cycle of the thread pool is the same as that of the whole system.