03. Detailed explanation of the usage of thread pool ThreadPoolExecutor class

We have to create threads when they are in use, and destroy them when they are not in use. Such frequent use and destruction of threads will consume a lot of time and resources. How can we avoid creating and destroying threads frequently when using threads?

This requires thread pool technology: Put multiple threads into a pool, and the pool manages the creation, use, and destruction of these threads.

ThreadPoolExecutor

The core class of thread pool: ThreadPoolExecutor.

1. The parent class inheritance relationship of ThreadPoolExecutor:

1.ThreadPoolExecutor inherits AbstractExecutorService (abstract class)

2. AbstractExecutorService (abstract class), which implements the ExecutorService interface.

3.ExecutorService inherits the Executor interface

Executor is a top-level interface, in which only one method execute(Runnable) is declared, the return value is void, and the parameter is of type Runnable. It can be understood from the literal meaning that it is used to execute the task passed in;

The ExecutorService interface inherits the Executor interface and declares some methods: submit, invokeAll, invokeAny, shutDown, etc.;

The abstract class AbstractExecutorService implements the ExecutorService interface and basically implements all the methods declared in the ExecutorService;

2. The construction method of ThreadPoolExecutor:

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    
   
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    
}

Introduction to the parameters of the construction method:

  • corePoolSize: The size of the core pool. This parameter has a great relationship with the implementation principle of the thread pool described later. After the thread pool is created, by default, there is no thread in the thread pool. Instead, it waits for a task to arrive before creating a thread to perform the task, unless the prestartAllCoreThreads() or prestartCoreThread() method is called. As you can see from the name, it means pre-created threads, that is, corePoolSize threads or one thread are created before no tasks arrive. By default, after the thread pool is created, the number of threads in the thread pool is 0. When a task comes, a thread will be created to execute the task. When the number of threads in the thread pool reaches corePoolSize, it will reach The tasks are placed in the cache queue;
  • maximumPoolSize: The maximum number of threads allowed in the thread pool. If the current blocking queue is full and the task continues to be submitted, a new thread is created to execute the task, provided that the current number of threads is less than the maximumPoolSize; when the blocking queue is an unbounded queue, the maximumPoolSize will not work because it cannot be submitted to the core thread pool The thread will be continuously put into the workQueue.
  • keepAliveTime: Indicates how long the thread keeps at most when there is no task execution will terminate. By default, keepAliveTime will work only when the number of threads in the thread pool is greater than corePoolSize, until the number of threads in the thread pool is not greater than corePoolSize, that is, when the number of threads in the thread pool is greater than corePoolSize, if a thread is idle When the time reaches keepAliveTime, it will be terminated until the number of threads in the thread pool does not exceed corePoolSize. But if the allowCoreThreadTimeOut(boolean) method is called, when the number of threads in the thread pool is not greater than corePoolSize, the keepAliveTime parameter will also work until the number of threads in the thread pool is 0;
  • unit: The time unit of the parameter keepAliveTime. There are 7 values. There are 7 static properties in the TimeUnit class:
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
 
  • workQueue: A blocking queue used to store tasks waiting to be executed. The choice of this parameter is also very important and will have a significant impact on the running process of the thread pool. Generally speaking, there are several options for the blocking queue here:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue and PriorityBlockingQueue are used less frequently, and LinkedBlockingQueue and Synchronous are generally used. The queuing strategy of the thread pool is related to BlockingQueue.

  • threadFactory: thread factory, mainly used to create threads;
  • handler: Indicates the strategy when the task is refused to be processed, with the following four values:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

3. There are several important methods in the ThreadPoolExecutor class:

1.execute()

The execute() method is actually a method declared in Executor, which is implemented in ThreadPoolExecutor. This method is the core method of ThreadPoolExecutor. Through this method, a task can be submitted to the thread pool and executed by the thread pool.

2.submit()

The submit() method is a method declared in ExecutorService. AbstractExecutorService already has a specific implementation. It is not rewritten in ThreadPoolExecutor. This method is also used to submit tasks to the thread pool, but it and execute( The) method is different. It can return the result of task execution. If you look at the implementation of the submit() method, you will find that it is actually the execute() method called, but it uses Future to get the task execution result.

3.shutdown()和shutdownNow()

  • shutdown(): The thread pool will not be terminated immediately, but it will be terminated after all tasks in the task cache queue have been executed, but no new tasks will be accepted
  • shutdownNow(): immediately terminate the thread pool, and try to interrupt the task being executed, and clear the task cache queue, return to the task that has not yet been executed

In the ThreadPoolExecutor class, the core task submission method is the execute() method. Although the task can also be submitted through submit, the final call in the submit method is the execute() method:

public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    //1.获取当前正在运行的线程个数
    int c = ctl.get();
    //2.判断当前线程数是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    
    
        //2.1 新建一个线程执行任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //3.核心池已满,但任务队列未满,添加到队列中
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))
            //3.2如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
            reject(command);
        else if (workerCountOf(recheck) == 0)
            //3.3如果之前的线程已被销毁完,新建一个线程
            addWorker(null, false);
    }
    else if (!addWorker(command, false)) //4.核心池已满,队列已满,试着创建一个新线程
        reject(command);//如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

4. List of main attributes of ThreadPoolExecutor class

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize; //线程池支持的最大线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

5. Task cache queue and queuing strategy

We have mentioned the task cache queue many times before, that is, workQueue, which is used to store tasks waiting to be executed.

The type of workQueue is BlockingQueue, which can usually be selected from the following three types:

1) ArrayBlockingQueue: Array-based first-in first-out queue, the size of this queue must be specified when it is created;

2) LinkedBlockingQueue: First-in-first-out queue based on linked list. If the queue size is not specified when creating, it will default to Integer.MAX_VALUE;

3) SynchronousQueue: This queue is special, it will not save submitted tasks, but will directly create a new thread to execute new tasks.

6. Task rejection strategy

When the task buffer queue of the thread pool is full and the number of threads in the thread pool reaches the maximumPoolSize, if there are tasks coming, the task rejection strategy will be adopted. There are usually the following four strategies:

  • ThreadPoolExecutor.AbortPolicy: Discard the task and throw RejectedExecutionException.
  • ThreadPoolExecutor.DiscardPolicy: also discards tasks, but does not throw exceptions.
  • ThreadPoolExecutor.DiscardOldestPolicy: Discard the first task in the queue, and then try to execute the task again (repeat this process)
  • ThreadPoolExecutor.CallerRunsPolicy: The task is rejected by the thread pool, and the task is executed by the thread that calls the execute method.

7. Common factory methods

In order to facilitate the use of thread pools, several factory methods for thread pools are provided in Executors. In this way, many novices do not need to know much about ThreadPoolExecutor. They only need to use the factory methods of Executors directly, and they can use it. Thread Pool:

  • newFixedThreadPool: This method returns a fixed number of thread pools, the number of threads remains unchanged, when a task is submitted, if the thread pool is free, it will be executed immediately, if not, it will be suspended in a task queue, waiting for free Thread to execute.
  • newSingleThreadExecutor: Create a thread pool, if it is idle, execute it, if there is no idle thread, it will be suspended in the task queue.
  • newCachedThreadPool: returns a thread pool that can adjust the number of threads according to the actual situation, does not limit the maximum number of threads, if idle threads are used, tasks are executed, and if there are no tasks, no threads are created. And every idle thread will be automatically recycled after 60 seconds
  • newScheduledThreadPool: Create a thread pool that can specify the number of threads, but this thread pool also has the function of delaying and periodic execution of tasks, similar to timers.

7. How to reasonably configure the size of the thread pool

How to configure the thread pool size reasonably, generally needs to configure the thread pool size according to the type of task:

1. If it is a CPU-intensive task, you need to squeeze the CPU as much as possible, and the reference value can be set to N CPU+1

2. If it is an IO-intensive task, the reference value can be set to 2 * N CPU
 
to provide you with a nice formula:

  • Optimal number of threads = ((thread waiting time + thread CPU time)/thread CPU time) * CPU number

For example: our server CPU core number is 4 cores, a task thread cpu takes 20ms, thread waiting (network IO, disk IO) takes 80ms, then the optimal number of threads: (80 + 20 )/20 * 4 = 20. That is, the optimal number of threads is set to 20.

Guess you like

Origin blog.csdn.net/weixin_43828467/article/details/114323138