Java thread pool understanding and example use (detailed)

Small chat: The concept of thread pool is still very important. "The idea of ​​pooling technology is mainly to reduce the consumption of resources each time and improve the utilization of resources. Thread pool, database connection pool, Http connection pool, etc. are all The application of this idea." This sentence is well evaluated. Although it is very commonly used, it does not take up much of the code time and is easy to forget. This article introduces the overall knowledge of the thread pool in detail and uses it with examples for easy recall.


1. Understand the thread pool

If you want the thread pool to perform tasks, you need to implement the Runnable interface or the Callable interface. Either the Runnable interface or the Callable interface implementation class can be executed by ThreadPoolExecutor or ScheduledThreadPoolExecutor. The difference between the two: the Runnable interface will not return results, but the Callable interface can return results.

The top-level interface of the thread pool in Java is java.util.concurrent.Executor, but strictly speaking, Executorit is not a thread pool, but just a tool class for executing threads. The real thread pool interface is java.util.concurrent.ExecutorService.


2. Executors thread pool

2.1. Common thread pool types
thread pool name describe
newSingleThreadExecutor() Create a single-threaded thread pool, which guarantees that the execution order of all tasks is executed in the order in which the tasks are submitted.
newFixedThreadPool(int nThreads) nThreads: number of threads. Create a fixed-size thread pool, and create a thread each time a task is submitted until the thread reaches the maximum size of the thread pool.
newCachedThreadPool() Create a cacheable thread pool. This thread pool does not limit the size of the thread pool. The size of the thread pool depends entirely on the maximum thread size that the operating system (or JVM) can create.
newScheduledThreadPool(int corePoolSize) Create a thread pool of unlimited size that supports timing and periodic execution of tasks.

2.2. Common thread pool methods
method explain
Future<?> submit(Runnable task) Get a thread object in the thread pool and execute it. The thread pool will return an object of future type, through which the task can be judged whether the execution is successful. (Future interface: used to record the results generated after the thread task is executed.)
public void execute(Runnable command) It is used to submit tasks that do not need to return a value, so it is impossible to judge whether the task is successfully executed by the thread pool.
public void shutdown() Note: The Executors class contains a set of methods that convert some other common closure-like objects, for example, java.security.PrivilegedAction into Callable forms for submitting them. Destroy or close the thread pool

2.3. Executors Instructions

It is more complicated to configure a thread pool, especially if the principle of the thread pool is not very clear, it is very likely that the configured thread pool is not optimal, so some static factories are provided in java.util.concurrent.Executorsthe thread Common thread pool. The official recommendation is to use Executorsengineering classes to create thread pool objects.

However, in the "Alibaba Java Development Manual", it is mandatory that the thread pool is not allowed to be used Executors, but is used ThreadPoolExecutorinstead, which will be described in detail below.


2.4. Example of Executors creating a thread pool (tool class)
class MyPool implements Runnable {
    
    
    @Override
    public void run() {
    
    
        System.out.println("线程" + Thread.currentThread().getName() + "的run方法正在运行");
    }
}
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个固定大小的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        MyPool pool = new MyPool();
        es.submit(pool);
        es.submit(pool);
        es.submit(pool);
        // 关闭线程池
        es.shutdown();
    }
}

输出
线程pool-1-thread-1的run方法正在运行
线程pool-1-thread-2的run方法正在运行
线程pool-1-thread-1的run方法正在运行

3. ThreadPoolExecutor thread pool

3.1. Understanding ThreadPoolExecutor

The mandatory thread pool in the "Alibaba Java Development Manual" does not allow the use of Executors tool classes to create, because there is no or only one method parameter, which is difficult to meet the scene requirements. Instead, through ThreadPoolExecutor , many input parameters are provided, and the thread pool can be customized. This processing method can clarify the running rules of the thread pool and avoid the risk of resource exhaustion.

The disadvantages of Executors.xxxxxx returning thread pool objects are as follows:
newFixedThreadPool and newSingleThreadExecutor : The queue length of the allowed request is Integer.MAX_VALUE, which may accumulate a large number of requests, resulting in OOM (out of memory).
newCachedThreadPool and newScheduledThreadPool : The number of threads allowed to be created is Integer.MAX_VALUE, a large number of threads may be created, resulting in OOM .


3.2. ThreadPoolExecutor constructor source code
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    
    
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • Description of source code parameters (can be understood in conjunction with the above examples)
parameter describe
corePoolSize The number of core threads, the minimum number of threads that can run at the same time. The number of threads to keep in the pool even if the thread is idle, unless allowCoreThreadTimeOut = true is set (default is false)
maximumPoolSize The maximum number of threads allowed to run concurrently in the pool
keepAliveTime When the number of threads is greater than corePoolSize, this is the maximum time that excess idle threads will wait for new tasks before terminating. Exceeded will be recycled and destroyed
unit The time unit of the keepAliveTime parameter
workQueue A queue for holding tasks before they are executed. The queue will only hold runnable tasks submitted by the execute method.
threadFactory The executor will use this factory class to create new threads. Default implementation of Executors.DefaultThreadFactory
handler saturation strategy. Handler to use when execution is blocked due to reaching thread boundaries and queue capacity
  • Exception description (see the above source code for understanding)

    1. If the condition corePoolSize < 0 || keepAliveTime < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize is met, an IllegalArgumentException will be thrown out of the valid range
    2. workQueue , threadFactory , handler , cannot be empty, otherwise NullPointerException will be thrown
  • Handler parameter usage: Saturation policy definition

    If the number of threads currently running at the same time reaches the maximum number of threads and the queue is full, ThreadPoolTaskExecutor defines some strategies to deal with the problem:

    parameter value describe
    ThreadPoolExecutor.CallerRunsPolicy Call to execute its own thread to run the task, that is, to run (run) the rejected task directly in the thread calling the execute method. If the execution program is closed, the task will be discarded. Therefore, this strategy will reduce the speed of submitting new tasks and affect the overall performance of the program. Also, this strategy likes to increase the queue capacity. You can choose this strategy if your application can tolerate this delay and you cannot task drop any task requests.
    ThreadPoolExecutor.AbortPolicy Throws RejectedExecutionException to reject new tasks
    ThreadPoolExecutor.DiscardPolicy Instead of processing new tasks, it silently discards them.
    ThreadPoolExecutor.DiscardOldestPolicy Unless the executor is shut down, the oldest outstanding task request is discarded and execution is retried.

    Example of working principle: When Spring creates a thread pool through ThreadPoolTaskExecutor or directly through the constructor of ThreadPoolExecutor, when we configure the thread pool without specifying the RejectedExecutionHandler saturation policy, ThreadPoolExecutor.AbortPolicy is used by default. By default, ThreadPoolExecutor will throw RejectedExecutionException to reject new tasks, which means you will lose the processing of this task. For scalable applications, ThreadPoolExecutor.CallerRunsPolicy is recommended. This strategy gives us a scalable queue when the max pool is filled.


3.3. Working principle process of thread pool (important)

First of all, let's understand again:
corePoolSize (number of core threads) : the maximum value that can execute task requests at the same time;
maximumPoolSize (maximum number of threads) : generally larger than corePoolSize, including the number of core threads, the initial size is corePoolSize, in order to leave a certain Disaster recovery space. It's like there are 16 seats on the bus, but the maximum number of passengers can be more, because the extra ones can stand.
workQueue (queue capacity) : The part of the task that exceeds corePoolSize is calculated independently and is not included in maximumPoolSize, which can be understood as passengers queuing in front of the bus.

  1. When 线程池线程执行数 <= corePoolSize(核心线程数): Because the thread pool must maintain at least corePoolSize threads, he will create corePoolSize threads at the beginning, even if there are remaining idle threads that do nothing. Unless allowCoreThreadTimeOut = true is set (the default is false), core threads will use keepAliveTime to time out waiting for work.
  2. When corePoolSize(核心线程数) < 线程池线程执行数 <= corePoolSize(核心线程数)+ workQueue(队列容量): The part exceeding the corePoolSize will be put into the queue you specify, and wait for the core thread to finish executing before replenishing.
  3. When 线程池线程执行数 > workQueue(队列容量)+ corePoolSize(核心线程数): Additional new task requests will be placed in the thread pool until the maximumPoolSize (maximum number of threads) is reached. At this time, the tasks will be executed by creating new threads to reduce the pressure on the thread pool.
  4. When the thread pool pressure decreases, the newly created threads in the largest thread pool will be idle. At this time, if you configure allowCoreThreadTimeOut = true (the default value is false), when the idle state (not doing work) exceeds your setting keepAliveTime(最大等待执行任务时间), these idle threads will be destroyed to release memory.
  5. When 线程池线程执行数 > workQueue(队列容量)+ maximumPoolSize最大线程数(): new tasks will be processed by the saturation policy you set (scale queue, replace or reject)

3.4. Example of using ThreadPoolExecutor thread pool
class MyPool implements Runnable {
    
    
    @Override
    public void run() {
    
    
        System.out.println("线程" + Thread.currentThread().getName() + "的run方法Start");
        working();
        System.out.println("线程" + Thread.currentThread().getName() + "的run方法End");
    }

    // 模拟线程执行耗时5s
    private void working() {
    
    
        try {
    
    
            Thread.sleep(5000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

/**
 * @Author 白忆宇
 */
public class Demo02 {
    
    
    private static final int CORE_POOL_SIZE = 5; // 核心线程数
    private static final int MAX_POOL_SIZE = 10; // 最大线程数
    private static final Long KEEP_ALIVE_TIME = 1L; // 等待新任务的最大时间(1s)
    private static final int QUEUE_CAPACITY = 50; // 任务队列为ArrayBlockingQueue,容量50;

    public static void main(String[] args) {
    
    
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS, // 等待时间单位
                new ArrayBlockingQueue<>(QUEUE_CAPACITY), // 使用初始固定容量的队列
                Executors.defaultThreadFactory(), // 线程工厂类(默认值)
        new ThreadPoolExecutor.CallerRunsPolicy()); // 饱和策略
        // 开启10个线程
        for (int i = 0; i < 10; i++) {
    
    
            //创建MyPool对象
            MyPool worker = new MyPool();
            // 执行线程
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
        // 保持主线程不结束,等其它协程结束
        // isTerminated()方法说明:
        // 如果该执行器在shutdown或shutdownNow后正在终止,但尚未完全终止,返回true。一般用于调试
        while (!executor.isTerminated()) {
    
    
        }
        System.out.println("多线程执行完毕");
    }
}
// 输出
线程pool-1-thread-1的run方法Start
线程pool-1-thread-3的run方法Start
线程pool-1-thread-4的run方法Start
线程pool-1-thread-2的run方法Start
线程pool-1-thread-5的run方法Start
线程pool-1-thread-4的run方法End
线程pool-1-thread-5的run方法End
线程pool-1-thread-2的run方法End
线程pool-1-thread-2的run方法Start
线程pool-1-thread-1的run方法End
线程pool-1-thread-3的run方法End
线程pool-1-thread-1的run方法Start
线程pool-1-thread-5的run方法Start
线程pool-1-thread-4的run方法Start
线程pool-1-thread-3的run方法Start
线程pool-1-thread-4的run方法End
线程pool-1-thread-1的run方法End
线程pool-1-thread-2的run方法End
线程pool-1-thread-5的run方法End
线程pool-1-thread-3的run方法End
多线程执行完毕

Explanation of the output process: because the number of core threads is set to 5, 5 lines will be printed at the same time first, and then 10 lines will be printed at the same time after 5 seconds of execution (the five threads that have ended and the five threads that have started), and then executed after the last 5 seconds Print 5 lines at the same time, and 5 threads after the end. A total of 20 lines, the start and end of 10 threads.

4. Benefits of using thread pool

  1. Reduce resource consumption. Reduce the cost of thread creation and destruction by reusing created threads.
  2. Improve responsiveness. When a task arrives, the task can be executed immediately without waiting for the thread to be created.
  3. Improve thread manageability. Threads are scarce resources. If created without limit, it will not only consume system resources, but also reduce the stability of the system. Using thread pool can be used for unified allocation, tuning and monitoring.

5. Essay

insert image description here

Guess you like

Origin blog.csdn.net/m0_48489737/article/details/127698480