Chapter 21 Understanding of the ThreadPoolExecutor source code

Preface

I haven’t had time to read and learn the basic core knowledge of Java for some time. The main reason is that I recently changed jobs, then finished my work, and got busy getting married. I just finished my life’s major events in October. I thought I could finally take a break, but the little bybe came again. , It seems there is no time to rest. Since there is no time to rest, I can only find time to study in the daily interval.

The following chapter will analyze Java's thread pool source code, as well as some principle analysis, if there are any errors, please correct me. First look at the family system commonly used in thread pools.
Insert picture description here

In the common family architecture, Executor is the top-level interface of the thread pool, and ExecutorService is also the interface. Our commonly used thread pool entity classes are ThreadPoolExecutor, ScheduledThreadPoolExecutor and Executors. Executors provides many methods for creating thread pools, but their internals are all based on ThreadPoolExecutor. There is also ScheduledThreadPoolExecutor, a timed thread pool, which is also inherited from ThreadPoolExecutor, so for those who study thread pools, the research and analysis of ThreadPoolExecutor is the top priority.

ThreadPoolExecutor 序

It is like a huge machine, but it is not good at describing specific images, so I liken it to an aircraft carrier.
The characteristics of aircraft carriers are that they are very large, their composition structure is very complex, and their operating mechanisms are cumbersome, and they all have a platform function. ThreadPollExecutor is the same as an aircraft carrier. As a carrier, it can carry some fighters. Each fighter can perform different tasks. It also corresponds to many states at different times.
This is the general description of the aircraft carrier ThreadPoolExecutor. The following will be divided into structural composition, working principle, and implementation mechanism to make a general analysis.

1. Structure composition

The skeleton of ThreadPollExecutor consists of six parts.

Core thread count, maximum thread count, thread survival time, task queue, thread factory, rejection strategy.
Insert picture description here
Is it like a robot? There are patterns and shapes, the shape of a large machine, but how to design each part to meet the needs, you need to design the parameters of each part.
So what is the meaning of each parameter? What is the relationship between the parameters?

故事背景: 
近年来,索马里海盗猖獗,许多的商货运输轮船都受到劫持,部分轮船还有人员伤亡,为此某部队准备派遣一艘航空母舰前往打击这些海盗。

1. Task queue
workQueue: a queue for storing tasks.

所有关于海盗的任务由总部收集信息后,发向航空母舰。航母可以设置一个任务接收队列。BlockingQueue 是一个阻塞队列的顶层接口,其实现有很多种,常见的几种队列:有界队列、无界队列、延迟队列、同步队列等等。

(1) Representation of bounded queues: ArrayBlockingQueue and LinkedBlockingQueue
have a certain length. The bottom layer of ArrayBlockingQueue is implemented by an array, and the size must be specified. The default size of LinkedBlockingQueue is Integer.MAX_VALUE, which is a task queue with a fixed limit.

(2) Representative of unbounded queue: PriorityBlockingQueue
is characterized by no boundaries and no capacity signs.

设计好了任务系统后,下一步就是飞机了。
飞机是航母上的核心,那么看一下飞机怎么生产出来的,即线程池中的线程。

2. Thread Factory
threadFactory: The factory for creating threads. It is a variable in the thread pool. ThreadFactory is also an interface that defines the only top-level method newThread().
There are two ways to configure this thread factory:
(1) Use the commonly used thread factory implementation class DefaultThreadFactory.
(2) The implementation of self-defined classes can be more personalized to meet the needs of the business.

飞机生产工厂有了,但是不可能让工厂无限制的生产,毕竟每架飞机耗费的资金巨大。所以需要根据业务的需要确定生产个数。
由于通常情况下,任务的数量是浮动的,所以生产的个数需要由两个参数来控制,即核心线程数和最大线程数。

3. The number of core threads
corePoolSize: represents the number of initial core worker threads.

在平时,航母上只装载了几架飞机,虽然索马里的海盗猖獗,但是好在不多,派出的飞机足以将他们打击。
但是临近过年了,平时不做海盗的平民也来当海盗了,越来越多的海盗出现在了索马里。航母上的飞机一下子忙不过来了。
这时候开始向总部请求支援飞机,但是最多可以支援多少呢,这取决于航母设计时甲板的容纳量。

4. Maximum number of threads
maximumPoolSize: Represents the maximum number of worker threads that can accommodate core threads.
Triggering condition : The core thread is used up, and the task queue is full.

增加了飞机后,有两个可能性。
(1)战斗力增强后,所有海盗都能够被打击了。
(2)即使甲板的飞机容量已经达到上限了,海盗们还是打击不过来。

按照上述第一个可能性,增派了飞机,海盗都能够被打击了,工作又恢复到平日里的样子。随着过年那段时间一过,平日里海盗又没有那么多了。
本该高兴的事情,舰长开始愁了,后面增派飞机后,每个月都要增加保养、加油、训练,还得让食堂多准备些馒头,这些飞行员真能吃,一口气吃仨个。
所以舰长想了想得让那些待业的飞机回去。

5. Thread survival time
keepAliveTime: the length of time for the thread to be recycled after being idle.
unit: This is the time unit. Cooperate with keepAliveTime to form a time.

这个参数是舰长想出来的一个损招,发出了一个全员通告:任何飞机,如果没有那么多海盗了,待机满XX天后必须返回总部。
这是发给所有飞机的一个共同的通告,但是为了维持日常任务需要,还是会留下核心数量多的飞机,这样食堂的馒头也不用抢了,舰长打着自己的小算盘。
但是既然是发给所有飞机的通告,那么当索马里没有海盗了,核心数量多的飞机需要回去吗?这取决于allowCoreThreadTimeOut这个参数是否为true。

6. Rejection policy
handler: Rejection policy after the thread pool is full.
Trigger condition: the thread is used up and the task queue is full before it will be triggered. So when the unbounded blocking queue is used, the rejection strategy is almost never triggered.

按照刚刚的第二种可能性,如果所有飞机都派完了,任务队列也装满了,总部不断发出的任务怎么办?

There are four rejection policies, namely AbortPolicy, DiscardPolicy, DiscardOldestPolicy, CallerRunsPolicy. They all implement the RejectedExecutionHandler interface and implement their own rejectedExecution() method. The implementation of this method for each rejection strategy is its specific rejection implementation details.

AbortPolicy : The default rejection policy of the thread pool. It means that an exception is always thrown.
DiscardPolicy : Its rejectedExecution() method does nothing, so it will directly cause the task to be discarded.
CallerRunsPolicy : This kind of rejection policy will directly execute the task in the caller's thread when the thread is not closed. If the thread is closed, it is like DiscardPolicy that causes the task to be discarded.
DiscardOldestPolicy : The last type of rejection policy means that if the thread pool is not closed, new tasks will be added to the end of the task queue, and tasks at the head of the queue will be crowded out. If the thread pool is closed, it is like DiscardPolicy.

2. Working principle

According to the characteristics of the above parameters, the operation of the thread pool is divided into the following parts, and examples are introduced to facilitate understanding:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
										   (corePoolSize)5,
											(maximumPoolSize) 20,
											(keepAliveTime) 30,
											(unit) TimeUnit.SECONDS,
											(workQueue) new LinkedBlockingDeque<>(),
											(threadFactory) Executors.defaultThreadFactory(),)

(1) Just booted, there is no task .
Just boot, that is, when the thread pool has just been created, no threads are created in the thread pool at this time. It's just an empty pool, which is on standby at this time.
Insert picture description here

Some people think that after the thread pool is created, the core thread pool will be created immediately. Actually not. When will the core thread be created? The next step will be a specific analysis.
(2) The number of tasks received at the same time <= the number of core threads [5].
When the first task comes in, since the thread pool was empty before, the thread factory will be used to immediately create a thread to execute this task.

思考? 按上面的理论,当第二个任务传来时,也会立即创建第二个线程吗?答:非也!

If the second task comes and the first thread is still executing, the thread pool will then create a second thread. If the first thread is idle, the first thread is still used to perform the task.
The same is true for the latter threads. Within the scope of this condition, they are reused if they can be reused, and new core threads are created when they cannot be reused.

If the number of tasks continues to increase, please see the next analysis.

(3) When the number of tasks received at the same time> the number of core threads, there are four cases: a, b, c, and d.
a. Number of tasks <= (number of core threads [5] + task queue length [2^31-1]) In
this case, since the core threads are all running, subsequent tasks will be added to the task blocking queue.
If some core threads finish running tasks, they will continue to take tasks out of the queue. Therefore, the operation of taking tasks in the queue may be a multi-threaded concurrent behavior. In order to ensure thread safety, a blocking queue is used instead of a traditional queue.

b. Number of tasks> (number of core threads [5] + task queue length [2^31-1])
With the sudden explosive growth of tasks, the queue has been filled up and cannot meet the demand for task growth.
At this time, the thread pool will create temporary threads to meet the needs of the task.

思考:此时的临时线程执行的任务是队列中poll出来的任务,还是任务队列装满后多余出来的任务?

This problem can be found in the method execute(Runnable command). At this time, the temporary thread executes the task that cannot be added to the queue. It is a redundant task. As long as another task is consumed in the queue, subsequent tasks will be added to the queue first.

c. Number of tasks <= (maximum number of threads [20] + task queue length [2^31-1]) The
threads in the thread pool will follow the strategy of b. If temporary threads are needed, the thread pool will continue to create these temporary threads . The maximum number of threads created depends on the parameter maximumPoolSize.
Once found to exceed this parameter. The following d situation will occur.

d. Number of tasks> (Maximum number of threads [20] + length of task queue [2^31-1])
If the queue is full at this time, the number of threads has reached the maximum number. The thread pool provides four rejection strategies for this situation. The caller can implement business requirements by setting rejection strategies and judging rejection strategies.

(4) Thread recovery mechanism The recovery mechanism of
core threads and temporary threads is the same. The recycling mechanism is determined in two ways.

  1. Whether to recycle
  2. How long will it take to recycle

In response to the first problem, the thread pool stipulates that temporary threads must be recycled. The core thread is based on the allowCoreThreadTimeOut value set by the user to determine whether to recycle.
For the second question, the recovery time of core threads and temporary threads are the same, which is determined by keepAliveTime + unit.

3. Implementation mechanism

The thread pool may seem complicated, but the internals are very simple. As long as you figure out the operating principle of the above thread pool, and understand the source code with the principle, it is like a familiar road.
First look at the internal member variables, I will divide them into regular variables and special variables.

variable

Conventional variables:

private final BlockingQueue<Runnable> workQueue; 				// 任务队列
private final ReentrantLock mainLock = new ReentrantLock();		// 线程池的主锁
private final Condition termination = mainLock.newCondition();	// 配合线程池主锁的释放器
private final HashSet<Worker> workers = new HashSet<Worker>();	// 线程的存储容器 
private int largestPoolSize;									// 最大线程数量
private long completedTaskCount;								// 线程池以及执行完成的任务个数
private volatile ThreadFactory threadFactory;					// 线程池的线程工厂
private volatile RejectedExecutionHandler handler;				// 拒绝策略
private volatile long keepAliveTime;							// 线程空闲后的回收时间限制
private volatile boolean allowCoreThreadTimeOut; 				// 是否允许核心线程超时后进行回收
private volatile int corePoolSize;								// 线程池的核心线程数量
private volatile int maximumPoolSize;							// 线程池的最大线程数量
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();// 默认的拒绝策略

Special variable

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 一个原子类的CAS操作
private static final int COUNT_BITS = Integer.SIZE - 3;					// COUNT_BITS 为29,表示int类型二进制中,其中29位用来表示线程池中线程的最大运行数量,剩余3位用来表示线程池状态。
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;			// 线程池中的线程最大容量
private static final int RUNNING    = -1 << COUNT_BITS;		// 运行态,二进制111 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;		// 关闭态,二进制000 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;		// 停止态,二进制001 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;  	// 整理态,二进制010 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS; 	// 结束态,二进制011 00000000000000000000000000000 

About the design ideas of special variables, some clues may have been seen from the above.

  1. The designer wants to use int type values ​​to represent the current running state of the thread pool and the number of threads running. Among the 32-bit bytes of the int type, the first 3 bits are used to indicate the status, and the last 29 bits are used to indicate the quantity.
  2. The ctl variable is such an integrated atomic variable, and from the
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); it
    can be seen that after the thread pool is created, the default is RUNNING state, and the number of threads is 0. This also explains why in the above principle, the thread pool is not created just after the thread pool is created, but the reason is an empty thread pool.
  3. From the point of view of the design of the state value, the RUNNING state is the smallest, and the state larger than it is the non-RUNNING state. Some parts of all the following source code are judged by the RUNNING state and the non-RUNNING state.

Constructor

The constructor simply assigns values ​​to variables and has several overloads.

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

Worker (inner class)

This is the core of the thread pool. We understand it as a worker, integrating Thread and Runnable.
So it can use the obtained thread to execute the received task.

final Thread thread;
Runnable firstTask;
volatile long completedTasks;

Constructing a Worker requires a task, so this task is the first task it needs to perform.

Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);		// 利用线程工厂创建。
    }

There are tasks, and there are threads. Then Worker can use threads to perform tasks, how to perform it?

public void run() {
        runWorker(this);
    }
    
官方注释:
主工作程序运行循环。从队列中反复获取任务并执行它们,同时处理一些问题:
1. 我们可以从一个初始任务开始,在这种情况下,我们不需要获得第一个任务。否则,只要池在运行,我们就会从getTask获得任务。如果返回null,则工作程序将由于池状态或配置参数的更改而退出。其他退出是由于抛出外部代码中的异常导致的,在这种情况下complete突然保持,这通常会导致processWorkerExit替换此线程。
2.在运行任何任务之前,会获得锁,以防止任务执行时其他池中断,然后我们确保除非池停止,否则这个线程不会设置它的中断。
3.在每个任务运行之前,都会调用beforeExecute,这可能会抛出一个异常,在这种情况下,我们会导致线程在不处理任务的情况下死亡(用completedsuddenly true中断循环)。
4.假设beforeExecute正常完成,我们运行任务,收集它抛出的任何异常发送到afterExecute。我们分别处理RuntimeException、Error(规范保证会捕获这两者)和任意可抛掷事件。因为我们不能在Runnable.run中重新抛出可抛弃物,所以我们在抛出时将它们包装在错误中(到线程的UncaughtExceptionHandler中)。保守地说,抛出的任何异常都会导致线程死亡。
5.在task.run完成后,我们调用afterExecute,这也会抛出一个异常,这也会导致线程死亡。根据JLS第14.20节,即使task.run抛出,这个异常也会生效。异常机制的最终效果是,在执行后,线程的UncaughtExceptionHandler提供了我们所能提供的关于用户代码遇到的任何问题的同样准确的信息。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

…The
detailed source code will not be analyzed, and I will come back later.
The main API is analyzed here.

execute method

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * 分3个步骤进行:
     *
     * 1.如果运行的线程少于corePoolSize,则尝试使用给定命令作为其
     * 第一个任务启动一个新线程。对addWorker的调用会自动检查runState和
     * workerCount,从而通过返回false防止在不应该添加线程的情况下添加线程的错误警报。
     *
     * 2.如果任务可以成功排队,那么我们仍然需要再次检查是否应该添加一个线程
     * (因为现有的线程在上次检查后死亡),或者池在进入此方法后关闭。因此,我们会
     * 重新检查状态,如果停止队列,必要时回滚队列;如果没有线程,则启动一个新线程。
     *
     * 3. 如果它失败了,我们知道我们被关闭或饱和,因此拒绝这个任务。
     */
    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);
}

submit method

The submit method of ThreadPoolExecutor is inherited from the parent class.

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

It can be seen from the inside that submit is the execute method that is called. The difference with execute is that submit returns a Future; the task can be manipulated through Future.

The difference between submit and execute

Guess you like

Origin blog.csdn.net/weixin_43901067/article/details/108033415