Java thread pool implementation principle and source code analysis

Java thread pool implementation principle and source code analysis


foreword

This article started writing in late November 2019, and it was delayed until the end of 2020, and it was not finished until the beginning of 2021.

Time is too fast and too slow~!

I vaguely remember that in October 2019, when a certain Dong left the startup company and planned to interview for a job, he asked me thread pool, would you know? Then he sent me a note I wrote in 2017 "Java Concurrent Programming Thread Pool Essential Knowledge Points", he said that's it? At that time, I thought there were almost so many thread pools~!

On November 9, 2019, a certain Dong and I took the 815 bus from Dawang Road to Yanjiao. At that time, it was just because I was learning some knowledge points related to multithreading, and we just chatted on the bus because there was nothing to do. At that time, he asked me some questions about the thread pool. I think that in the usual working thread pool, if you know how to use it, you can optimize the number of core threads at most. The main discussion is related to multi-threaded concurrency and locks.

At the end of the year, I was generally busy at work, so I seldom did self-study. After a week, I remembered the question asked by Dong Dong, "How are the threads in the thread pool generated, and how do tasks wait for execution?".

I am not very clear about the logic of this piece, so I temporarily recorded this TODO item, and I think I will study it when I have time. As a result, this spanned 2019 and 2020 and came directly to 2021.

Forgive my long-windedness, the key point is that the time span of this article is too long, and it has left a deep impression on me. I have to talk about it, let’s start to get down to business~!

JDK1.8The source code to analyze the core design and implementation of the Java thread pool.

This article refers to the implementation principle of Java thread pool and its practice in Meituan's business .

The implementation principle of Java thread pool and its practice in Meituan business This article is very well written. In addition to the content of this article, this article also describes the background of thread pool , the practice and dynamics of thread pool in business Therefore, if you want to know about these types of thread pools, you can read the article on the implementation principle of Java thread pools and its practice in Meituan's business .

If the reader is a student who is doing server-side development, it is strongly recommended to read the implementation principle of Java thread pool and its practice in Meituan business .

Exterior

The appearance is mainly some points that we usually see when using the thread pool.

  • Inheritance relationship;
  • Constructor;
  • parameters in the constructor;
  • Blocking queue in constructor;
  • Creation of thread pool;
  • Deny policy in constructor;

thread pool inheritance

ThreadPoolExecutor-uml.png

ThreadPoolExecutorThe top-level interface implemented is that Executorin the interface, Executorusers do not need to pay attention to how to create threads and how to schedule threads to execute tasks. Users only need to provide Runnableobjects, submit the task operation logic to the executor Executor, and Executorthe framework completes the deployment of threads and the execution of tasks part.

ExecutorServiceThe interface adds some capabilities:

  1. Extend the ability to execute tasks, supplementing Futuremethods that can be generated for one or a batch of asynchronous tasks;
  2. Provides methods to manage and control the thread pool, such as stopping the operation of the thread pool.

AbstractExecutorServiceIt is the abstract class of the upper layer, which connects the process of executing tasks in series, ensuring that the implementation of the lower layer only needs to focus on one method of executing tasks.

The lowest-level implementation class ThreadPoolExecutorimplements the most complex operation part:

  1. A set of specified number of threads can be automatically created, managed and reused, and the applicable party only needs to submit the task

  2. Thread safety, ThreadPoolExecutorinternal state, number of core threads, non-core threads and other attributes, extensive use of CAS and AQS lock mechanisms to avoid conflicts caused by concurrency

  3. Provides the concepts of core threads, buffer blocking queues, non-core threads, and discarding strategies, which can be combined and used according to actual application scenarios

  4. Provides beforeExecuteand afterExecute()can support the extension of the function of the thread pool

Constructor

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;
}
  • corePoolSize : The number of core threads in the thread pool. Generally, no matter whether there are tasks or not, they will always survive in the thread pool. Only when ThreadPoolExecutorthe methodallowCoreThreadTimeOut(boolean value) in is set to , idle core threads will have a timeout mechanism. If there are no new tasks within the specified time trueWhen it comes, the core thread will also be terminated, and this time interval keepAliveTimeis specified .
  • maximumPoolSize : The maximum number of threads that the thread pool can accommodate. When the number of active threads reaches this value, subsequent new tasks will be blocked.
  • keepAliveTime : Controls the timeout period when the thread is idle. If it exceeds, the thread will be terminated. Generally used for non-core threads, ThreadPoolExecutoronly whenallowCoreThreadTimeOut(boolean value) the method in is set to , it also acts on core threads.true
  • unit : used to specify keepAliveTimethe time unit of the parameter, TimeUnitit is an enumenumeration type, commonly used are: TimeUnit.HOURS(小时), TimeUnit.MINUTES(分钟), TimeUnit.SECONDS(秒) and TimeUnit.MILLISECONDS(毫秒)etc.
  • workQueue : The task queue of the thread pool, execute(Runnable command)the task will be Runnablestored in the queue through the method of the thread pool.
  • threadFactory : thread factory, which is an interface used to create new threads for the thread pool.
  • handler : Rejection strategy. The so-called rejection strategy refers to the corresponding strategy adopted by the thread pool to reject the task when the task is added to the thread pool.

Member variables

/**
 * 任务阻塞队列 
 */
private final BlockingQueue<Runnable> workQueue; 
/**
 * 非公平的互斥锁(可重入锁)
 */
private final ReentrantLock mainLock = new ReentrantLock();
/**
 * 线程集合一个Worker对应一个线程,没有核心线程的说话,只有核心线程数
 */
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
 * 配合mainLock通过Condition能够更加精细的控制多线程的休眠与唤醒
 */
private final Condition termination = mainLock.newCondition();
/**
 * 线程池中线程数量曾经达到过的最大值。
 */
private int largestPoolSize;  
/**
 * 已完成任务数量
 */
private long completedTaskCount;
/**
 * ThreadFactory对象,用于创建线程。
 */
private volatile ThreadFactory threadFactory;  
/**
 * 拒绝策略的处理句柄
 * 现在默认提供了CallerRunsPolicy、AbortPolicy、DiscardOldestPolicy、DiscardPolicy
 */
private volatile RejectedExecutionHandler handler;
/**
 * 线程池维护线程(超过核心线程数)所允许的空闲时间
 */
private volatile long keepAliveTime;
/**
 * 允许线程池中的核心线程超时进行销毁
 */
private volatile boolean allowCoreThreadTimeOut;  
/**
 * 线程池维护线程的最小数量,哪怕是空闲的  
 */
private volatile int corePoolSize;
/**
 * 线程池维护的最大线程数量,线程数超过这个数量之后新提交的任务就需要进入阻塞队列
 */
private volatile int maximumPoolSize;

Create a thread pool

ExecutorsProvides methods to obtain several commonly used thread pools:

  • Cache thread pool

newCachedThreadPoolIs a thread pool that creates new threads as needed, but reuses previously constructed threads as they become available. For programs that perform many short-lived asynchronous tasks, these thread pools often improve program performance. Calling execute()will reuse a previously constructed thread (if a thread is available). If no existing thread is available, a new thread is created and added to the pool. Terminates and removes threads that have not been used for 60 seconds from the cache. Therefore, a thread pool that remains idle for a long time will not use any resources. Note that thread pools with similar properties but different details (such as timeout parameters) can be created using the ThreadPoolExecutorconstructor .

public static ExecutorService newCachedThreadPool() {
    
    
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>());
}
  • single thread thread pool

newSingleThreadExecutorCreate a single-threaded pool, that is, the thread pool has only one thread working, and all tasks are executed serially. If the only thread ends abnormally, a new thread will replace it. This thread pool Ensure that the execution order of all tasks is executed in the order in which the tasks are submitted.

public static ExecutorService newSingleThreadExecutor() {
    
    
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()));
}
  • fixed size thread pool

newFixedThreadPoolCreate a fixed-size thread pool, and create a thread every time a task is submitted until the thread reaches the maximum size of the thread pool. Once the thread pool reaches the maximum size, it will remain unchanged. If a thread ends due to an abnormal execution, Then the thread pool will add a new thread.

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    
    
  return new ThreadPoolExecutor(nThreads, nThreads,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>(),
                  threadFactory);
}
  • single thread thread pool

newScheduledThreadPoolCreate a thread pool of unlimited size that supports timing and periodic execution of tasks.

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
  return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                   ThreadFactory threadFactory) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue(), threadFactory);
}

We can see that the above method uses a total of DelayedWorkQueue, LinkedBlockingQueueand SynchronousQueue. This is the blocking queue, one of the thread cores.

task blocking queue

BlockingQueue.png

It is generally divided into direct submission queues, bounded task queues, unbounded task queues, and priority task queues;

SynchronousQueue

1. Direct submission queue : set as SynchronousQueuea queue, SynchronousQueueit is a special one BlockingQueue, it has no capacity, it will be blocked every time an insert operation is performed, and it will be awakened only after another delete operation is performed, otherwise every delete operation must wait for the corresponding Insert operation.

Using SynchronousQueuethe queue, the submitted tasks will not be saved, and will always be submitted for execution immediately. If the number of threads used to execute the task is less than maximumPoolSize, try to create a new process, if it reaches maximumPoolSizethe set maximum value, it will handlerreject the execution according to the policy you set. Therefore, the tasks you submit in this way will not be cached, but will be executed immediately. In this case, you need to have an accurate assessment of the concurrency of your program before you can set the appropriate number, otherwise it is maximumPoolSizeeasy The rejection policy will be implemented;

ArrayBlockingQueue

2. Bounded task queue : A bounded task queue can be implemented ArrayBlockingQueueas follows:

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

Using ArrayBlockingQueuea bounded task queue, if a new task needs to be executed, the thread pool will create a new thread, until the number of created threads reaches corePoolSize, the new task will be added to the waiting queue. If the waiting queue is full, that is, exceeds ArrayBlockingQueuethe initialized capacity, continue to create threads until the number of threads reaches the maximumPoolSizemaximum number of threads set, and if it is larger maximumPoolSize, execute the rejection strategy. In this case, the upper limit of the number of threads is directly related to the state of the bounded task queue. If the initial capacity of the bounded queue is large or the state of overload is not reached, the number of threads will always be kept below e. Otherwise, when the task corePoolSizqueue When it is full, the maximum number of threads will maximumPoolSizebe capped.

LinkedBlockingQueue

3. Unbounded task queue : The unbounded task queue can be implemented LinkedBlockingQueueas follows:

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

Using an unbounded task queue, the task queue of the thread pool can add new tasks without limit, and the maximum number of threads created by the thread pool is the number you corePoolSizeset, that is to say, maximumPoolSizethis parameter is invalid in this case, even if your There are many unexecuted tasks cached in the task queue. When the number of threads in the thread pool reaches corePoolSize, it will not increase any more; if new tasks are added later, they will directly enter the queue and wait. When using this task queue mode, Be sure to pay attention to the coordination and control between your task submission and processing, otherwise there will be a problem that the tasks in the queue will continue to grow due to the inability to process them in time until the resources are exhausted.

PriorityBlockingQueue

4. Priority task queue : The priority task queue is PriorityBlockingQueueimplemented by:

Tasks will be rearranged and executed according to priority, and the number of threads in the thread pool is always corePoolSize, that is, there is only one.

PriorityBlockingQueueIn fact, it is a special unbounded queue. No matter how many tasks are added to it, the number of threads created by the thread pool will not exceed the maximum corePoolSizenumber, but other queues generally process tasks according to the first-in-first-out rule, and PriorityBlockingQueuethe queue can be customized The rules are executed sequentially according to the priority order of the tasks.

In fact, LinkedBlockingQueuethe limit can also be set, and its default limit is Integer.MAX_VALUE. At the same time, it also supports setting the queue size when constructing.

rejection policy

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

When Executorit has been closed, i.e. after the method has been executed executorService.shutdown(), or Executorwhen bounded bounds are used for the maximum thread and work queue capacity, and have been saturated. execute()New tasks submitted using the method will be rejected.
In the above case, executethe method will call its methodRejectedExecutionHandler .RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)

AbortPolicy default rejection policy

Also known as a termination policy, a rejection throws the runtime RejectedExecutionException. The business side can get timely feedback on the results submitted for this task by catching exceptions.

public static class AbortPolicy implements RejectedExecutionHandler {
    
    
  public AbortPolicy() {
    
     }
  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
  }
}

CallerRunsPolicy

Having autonomous feedback control that lets submitters perform submission tasks can slow down the submission of new tasks. In this case, it is necessary to let all tasks be executed.

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
    public CallerRunsPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            r.run();
        }
    }
}

DiscardPolicy

A handler for rejecting tasks, silently discarding tasks. Using this strategy, we may not be able to perceive the abnormal state of the system. Use with caution~!

public static class DiscardPolicy implements RejectedExecutionHandler {
    
    
    public DiscardPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    }
}

DiscardOldestPolicy

Discard the frontmost task in the queue and resubmit rejected tasks. Whether to use this strategy depends on whether the business needs to be replaced by the new and the old, so use it with caution~!

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    
    public DiscardOldestPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

kernel

I talked about the appearance of the thread pool earlier , and then I will talk about its core .

The thread pool actually builds a producer-consumer model internally , which will be decoupled 线程from 任务the two and not directly related, so as to buffer tasks well and reuse threads.

The operation of the thread pool is mainly divided into two parts: task management and thread management.

The task management part acts as a producer. When a task is submitted, the thread pool will judge the subsequent flow of the task:

  1. Directly apply for the thread to perform the task;
  2. Buffer into the queue and wait for the thread to execute;
  3. Decline the task.

The thread management part is the consumer, which is uniformly maintained in the thread pool, and the thread is allocated according to the task request. After the thread executes the task, it will continue to obtain new tasks to execute. Finally, when the thread cannot obtain the task, the thread will be recycled.

Next, we will explain the thread pool operation mechanism in detail according to the following three parts:

  1. How the thread pool maintains its own state.
  2. How the thread pool manages tasks.
  3. How the thread pool manages threads.

The life cycle of the thread pool

The running state of the thread pool is not explicitly set by the user, but is maintained internally along with the running of the thread pool.

The thread pool internally uses a variable to maintain two values: the running status ( runState) and the number of threads ( workerCount).

In the specific implementation, the thread pool puts together the maintenance of two key parameters, the running state ( runState) and the number of threads ( ):workerCount

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctlThis AtomicIntegertype is a field that controls the running status of the thread pool and the number of valid threads in the thread pool.

It contains two parts of information at the same time: the running state of the thread pool ( runState) and the number of effective threads in the thread pool ( workerCount), the upper 3 bits are saved runState, and the lower 29 bits are saved workerCount, and the two variables do not interfere with each other.

Using one variable to store two values ​​can avoid inconsistencies when making relevant decisions, and it is not necessary to occupy lock resources in order to maintain the consistency between the two. It can also be found by reading the source code of the thread pool that it is often necessary to judge the running status of the thread pool and the number of threads at the same time. The thread pool also provides several methods for the user to obtain the current running status and number of threads of the thread pool. The method of bit operation is used here, which is much faster than the basic operation (PS: this usage can be seen in many source codes).

The calculation method of obtaining the life cycle state and obtaining the number of thread pool threads in the internal package is shown in the following code:

private static final int COUNT_BITS = Integer.SIZE - 3;//32-3
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//低29位都为1,高位都为0

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;//111
private static final int SHUTDOWN   =  0 << COUNT_BITS;//000
private static final int STOP       =  1 << COUNT_BITS;//001
private static final int TIDYING    =  2 << COUNT_BITS;//010
private static final int TERMINATED =  3 << COUNT_BITS;//011

// Packing and unpacking ctl
//计算当前运行状态,取高三位
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
//计算当前线程数量,取低29位
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

ThreadPoolExecutorThere are 5 running states, namely:

Operating status status description
RUNNING Can accept newly submitted tasks, and can also process tasks in the blocking queue
SHUTDOWN Cannot accept newly submitted tasks, but can continue to process tasks in the blocking queue
STOP It cannot accept new tasks, nor can it process tasks in the queue while interrupting the processing task thread
TIDYING All tasks have been terminated, workCount (number of effective threads) is 0
TERMINATED Enter this state after the terminated method is executed

Thread pool statement cycle.jpg

Task Scheduling Mechanism

Task scheduling is the main entry point of the thread pool. When a user submits a task, how the task will be executed next is determined by this stage. Understanding this part is equivalent to understanding the core operating mechanism of the thread pool.

First of all, the scheduling of all tasks is executecompleted by the method. The work done in this part is: check the current running status of the thread pool , the number of running threads , and the running strategy , and determine the next execution process, whether to directly apply for thread execution , or Buffered to the queue for execution , or directly reject the task . Its execution process is as follows:

  1. First check the running status of the thread pool, if not RUNNING, directly reject it, and the thread pool must ensure that RUNNINGthe task is executed in a certain state.
  2. If workerCount < corePoolSize, a thread is created and started to execute the newly submitted task.
  3. If workerCount >= corePoolSize, and the blocking queue in the thread pool is not full, add the task to the blocking queue.
  4. If workerCount >= corePoolSize && workerCount < maximumPoolSize, and the blocking queue in the thread pool is full, create and start a thread to execute the newly submitted task.
  5. If workerCount >= maximumPoolSize, and the blocking queue in the thread pool is full, the task will be processed according to the rejection strategy, and the default processing method is to throw an exception directly.

Task scheduling flowchart.png

Next, enter the source code analysis time~!

submit task

//AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
//ThreadPoolExecutor.java
public void execute(Runnable command) {
    
    
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();//获取ctl
  //检查当前核心线程数,是否小于核心线程数的大小限制
  if (workerCountOf(c) < corePoolSize) {
    
    
    //没有达到核心线程数的大小限制,那么添家核心线程执行该任务
    if (addWorker(command, true))
      return;
    //如果添加失败,刷新ctl值
    c = ctl.get();
  }
  //再次检查线程池的运行状态,将任务添加到等待队列中
  if (isRunning(c) && workQueue.offer(command)) {
    
    
    int recheck = ctl.get();//刷新ctl值
    //如果当前线程池的装不是运行状态,那么移除刚才添加的任务
    if (! isRunning(recheck) && remove(command))
      reject(command);//移除成功后,使用拒绝策略处理该任务;
    else if (workerCountOf(recheck) == 0)//当前工作线程数为0
      //线程池正在运行,或者移除任务失败。
      //添加一个非核心线程,并不指定该线程的运行任务。
      //等线程创建完成之后,会从等待队列中获取任务执行。
      addWorker(null, false);
  } 
  //逻辑到这里说明线程池已经不是RUNNING状态,或者等待队列已满,需要创建一个新的非核心线程执行该任务;
  //如果创建失败,那么非核心线程已满,使用拒绝策略处理该任务;
  else if (!addWorker(command, false))
    reject(command);
}

Add worker threads and execute tasks

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
  Worker(Runnable firstTask) {
    
    
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;//初始化的任务,可以为null
    this.thread = getThreadFactory().newThread(this);//Worker持有的线程
  }
  /**部分代码省略*/
	public void run() {
    
    
  	runWorker(this);
	}
}

Adding workers and executing tasks : the whole thing is creating Worker, and finding a match for it Runnable.

addwork.png

Add worker thread

Adding threads is through the method in the thread pool addWorker. The function of this method is to add a thread. This method does not consider at which stage the thread pool adds the thread. This strategy of allocating threads is completed in the previous step. This step is only Finish increasing the thread, make it run, and finally return the result of whether it is successful or not.

addWorkerThe method has two parameters: firstTask, core.

firstTaskThe parameter is used to specify the first task executed by the newly added thread, which can be empty;

coreThe parameter truemeans that when adding a new thread, it will judge whether the number of current active threads is less than corePoolSize, falseand it means that it needs to judge whether the number of current active threads is less than before adding a new thread maximumPoolSize.

addwork2.png

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    retry://breakcontinue的跳出标签
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取当前线程池的状态;
        /**
         * 1、如果当前的线程池状态不是RUNNING
         * 2、当前线程池是RUNNING而且没有添加新任务,而且等待队列不为空。这种情况下是需要创建执行线程的。
         * 所以满足1,但不满足2就创建执行线程失败,返回false。
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;
        /**进入内层循环 */
        for (;;) {
    
    
            int wc = workerCountOf(c);//获取当前执行线程的数量
            /**
             * 1、工作线程数量大于或等于计数器的最大阈值,那么创建执行线程失败,返回false。
             * 2、如果当前创建的核心线程,那么工作线程数大于corePoolSize的话,创建执行线程失败,返回false。
             * 3、如果当前创建的是非核心线程,那么工作线程数大于maximumPoolSize的话,创建执行线程失败,返回false。
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //用CAS操作让线程数加1,如果成功跳出整个循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)//线程状态前后不一样,重新执行外循环
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
            //如果CAS操作由于工作线程数的增加失败,那么重新进行内循环
        }
    }
    /**就现在,线程数已经增加了。但是真正的线程对象还没有创建出来。*/
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();//加锁
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                /**
                 * 再次检查线程池的运行状态
                 * 1、如果是RUNNING状态,那么可以创建;
                 * 2、如果是SHUTDOWN状态,但没有执行线程,可以创建(创建后执行等待队列中的任务)
                 */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    //检测该线程是否已经开启
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);//将改工作线程添加到集合中
                    int s = workers.size();
                    if (s > largestPoolSize)//更新线程池的运行时的最大线程数
                        largestPoolSize = s;
                    workerAdded = true;//标识工作线程添加成功
                }
            } finally {
    
    //释放锁
                mainLock.unlock();
            }
            if (workerAdded) {
    
    //如果工作线程添加成功,那么开启线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        //如果工作线程添加失败,那么进行失败处理
        //将已经增加的线程数减少,将添加到集合的工作线程删除
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

perform tasks

We saw in the section of adding a worker thread that after the addition is successful, the thread will be started to execute the task.

runwork.png

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //解锁,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
        //如果当前的工作线程已经有执行任务,或者可以从等待队列中获取到执行任务
        //getTask获取任务时候会进行阻塞
        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
            //判断线程是否需要中断
            //如果线程池状态是否为STOP\TIDYING\TERMINATED,同时当前线程没有被中断那么将当前线程进行中断
            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++;//当前工作线程执行完成的线程数+1
                w.unlock();//执行完成解锁
            }
        }
        completedAbruptly = false;//完成了所有任务,正常退出
    } finally {
    
    //执行工作线程的退出操作
        processWorkerExit(w, completedAbruptly);
    }
}

The worker thread gets the task

getTask.jpg

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取线程池状态

        // Check if queue empty only if necessary.
        /**
         * 1、rs为STOP\TIDYING\TERMINATED,标识无法继续执行任务
         * 2、等待队列中没有任务可以被执行
         * 工作线程数量减一
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);//获取工作线程数量
        // Are workers subject to culling?
        //如果允许核心线程超时,或者当前工作线程数量大于核心线程数量。标识需要进行超时检测
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /**
         * 1、如果当前工作线程数是否大于线程池可允许的最大工作线程数(maximumPoolSize可以动态设置)
         * ,或者当前需要进行超时控制并且上次从等待队列中获取执行任务发生了超时。
         * 2、如果当前不是唯一的线程,并且等待队列中没有需要执行的任务。
         * 这两种情况下一起存在就表示,工作线程发生了超时需要回收,所以对线程数进行-1;
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))//线程数量减少成功,否则重新执行本次循环
                return null;
            continue;
        }
        try {
    
    
            //如果设置有超时,那么设定超时时间。否则进行无限的阻塞等待执行任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;//获取超时,设置标记
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}

worker thread exit

The destruction of threads in the thread pool depends on the automatic recycling of the JVM . The job of the thread pool is to maintain a certain number of thread references according to the current state of the thread pool to prevent these threads from being recycled by the JVM. When the thread pool decides which threads need to be recycled, only You need to remove its reference. WorkerAfter being created, polling will be performed continuously, and then the task will be acquired to execute. The core thread can wait indefinitely to acquire the task, and the non-core thread must acquire the task within a limited time. When Workerthe task cannot be obtained, that is, the obtained task is empty, the loop will end, and Workerit will actively eliminate its own reference in the thread pool.

processWorkerExit.png

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
    //completedAbruptly为true,标识该工作线程执行出现了异常,将工作线程数减一
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    //否则标识该工作线程为正常结束,这种情况下getTask方法中已经对工作线程进行了减一
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        completedTaskCount += w.completedTasks;//更新线程池的,线程执行完成数量
        workers.remove(w);//工作线程容器移除该工作线程
    } finally {
    
    
        mainLock.unlock();//解锁
    }
    //尝试结束线程池
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
    
    //如果当前线程池的运行状态是RUNNING\SHUTDOWN
        if (!completedAbruptly) {
    
    //如果该工作线程为正常结束
            /**
             * 判断当前需要的最少的核心线程数(如果允许核心线程超时,那么最小的核心线程数为0,否则为corePoolSize)
             */
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            //如果允许核心线程超时,而且等待队列不为空,那么工作线程的最小值为1,否则为0。
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            //当前工作线程数,是否满足最先的核心线程数
            if (workerCountOf(c) >= min)
                //如果满足那么直接return
                return; // replacement not needed
        }
        //如果是异常结束,或者当前线程数不满足最小的核心线程数,那么添加一个非核心线程
        //核心线程和非核心线程没有什么不同,只是在创建的时候判断逻辑不同
        addWorker(null, false);
    }
}

special needs

Thread pool monitoring

Monitor through the parameters provided by the thread pool. There are some attributes in the thread pool that can be used when monitoring the thread pool

  • getTaskCount: The total number of executed and unexecuted tasks in the thread pool;
  • getCompletedTaskCount: The number of tasks completed by the thread pool, the value is less than or equal to taskCount;
  • getLargestPoolSize: The maximum number of threads ever created by the thread pool. Through this data, we can know whether the thread pool is full, that is, it has been reached maximumPoolSize;
  • getPoolSize: The current number of threads in the thread pool;
  • getActiveCount: The number of threads currently executing tasks in the thread pool.

Dynamically adjust the size of the thread pool

JDKAllows the thread pool user ThreadPoolExecutorto dynamically set the core policy of the thread pool through the instance, setCorePoolSizeas an example of the method;

After the runtime thread pool user calls this method setting corePoolSize, the thread pool will directly overwrite the original corePoolSizevalue, and adopt different processing strategies based on the comparison between the current value and the original value.

For the case where the current value is less than the current number of working threads, it means that there are redundant workerthreads. At this time, an interrupt request will be initiated to the current idlethread workerto realize recycling, and the redundant ones will also be recycled next time; for the current value is greater than the original value and the workercurrent idelIf there are tasks to be executed in the queue, the thread pool will create a new workerthread to execute the queue tasks (PS: idelthe state is workerthe state after the thread releases the lock, because it is locked during operation).

setCorePoolSize.png

public void setCorePoolSize(int corePoolSize) {
    
    
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    //计算增量
    int delta = corePoolSize - this.corePoolSize;
    //覆盖原有的corePoolSize
    this.corePoolSize = corePoolSize;
    //如果当前的工作线程数量大于线程池的最大可运行核心线程数量,那么进行中断工作线程处理
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();
    else if (delta > 0) {
    
    //如果增量大于0
        // We don't really know how many new threads are "needed".
        // As a heuristic, prestart enough new workers (up to new
        // core size) to handle the current number of tasks in
        // queue, but stop if queue becomes empty while doing so.
        //等待队列非空,获取等待任务和增量的最小值
        int k = Math.min(delta, workQueue.size());
        //循环创建核心工作线程执行等待队列中的任务
        while (k-- > 0 && addWorker(null, true)) {
    
    
            if (workQueue.isEmpty())
                break;
        }
    }
}
private void interruptIdleWorkers() {
    
    
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        //遍历工作线程的集合
        for (Worker w : workers) {
    
    
            Thread t = w.thread;
            //如果当前线程没有被中断,而且能获取到锁,那么尝试进行中断,最后释放锁
            if (!t.isInterrupted() && w.tryLock()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                } finally {
    
    
                    w.unlock();
                }
            }
            //是否仅仅中断一个工作线程
            if (onlyOne)
                break;
        }
    } finally {
    
    //释放锁
        mainLock.unlock();
    }
}

Gracefully shut down the thread pool

It can also be seen from the "Thread Pool Statement Cycle" diagram that when we execute ThreadPoolExecutor#shutdownthe method the state of the thread pool will change from RUNNING to SHUTDOWN . ThreadPoolExecutor#shutdownNowAfter the call, the thread pool status will change from RUNNING to STOP .

shutdown

Stop accepting new tasks, and the original tasks continue to execute

  1. Stop receiving new submit tasks;
  2. The tasks that have been submitted (including those that are running and those waiting in the queue) will continue to be executed;
  3. Wait until step 2 is completed before actually stopping;
public void shutdown() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查权限
        advanceRunState(SHUTDOWN);// 设置线程池状态
        interruptIdleWorkers();// 中断空闲线程
        // 钩子函数,主要用于清理一些资源
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownThe method first locks, and then checks the system installation status first. Then the state of the thread pool will be changed to SHUTDOWN , after which the thread pool will no longer accept new tasks submitted. At this time, if you continue to submit tasks to the thread pool, the thread pool rejection policy will be used to respond, which will be used by default and an exceptionThreadPoolExecutor.AbortPolicy will be thrown .RejectedExecutionException

interruptIdleWorkersThe method is described in the source code in the section of dynamically adjusting the size of the thread pool . It will only interrupt idle threads, and will not interrupt threads that are executing tasks. Idle threads will be blocked on the thread pool's blocking queue.

shutdownNow

Stop accepting new tasks, and the original tasks stop executing

  1. Same shutdown() as , first stop receiving new submittasks;
  2. Ignore tasks waiting in the queue;
  3. Attempt to interrupt the executing task interrupt;
  4. Return a list of unexecuted tasks;
public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查状态
        advanceRunState(STOP);// 将线程池状态变为 STOP
        interruptWorkers();// 中断所有线程,包括工作线程以及空闲线程
        tasks = drainQueue();// 丢弃工作队列中存量任务
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (Worker w : workers)
            //如果工作线程已经开始,那么调用interrupt进行中断
            w.interruptIfStarted();
    } finally {
    
    
        mainLock.unlock();
    }
}
private List<Runnable> drainQueue() {
    
    
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    //从此队列中删除所有可用的元素,并将它们添加到给定的集合中。
    q.drainTo(taskList);
    //如果队列是DelayQueue或其他类型的队列,而poll或drainTo可能无法删除某些元素,则会将它们逐个删除。
    if (!q.isEmpty()) {
    
    
        for (Runnable r : q.toArray(new Runnable[0])) {
    
    
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

shutdownNowThe method of trying to terminate the thread is realized by calling Thread.interrupt()the method , which has limited effect. If there are no applications such as sleep, wait, , time lock in the thread, the method cannot interrupt the current thread. Therefore, it does not mean that the thread pool will be able to exit immediately, and it may have to wait for all executing tasks to complete before exiting. But most of the time it is possible to quit immediately.Conditioninterrupt()shutdownNow()

Thread interruption mechanism: thread#interruptjust setting an interruption flag will not immediately interrupt normal threads. If you want the interrupt to take effect immediately, you must call in the thread to Thread.interrupted()judge the interrupt status of the thread. For a blocked thread, when interrupt is called, the thread will immediately exit the blocked state and throw InterruptedExceptionan exception . So for blocked threads, InterruptedExceptionexceptions .

awaitTermination

The thread pool shutdownand shutdownNowmethod will not actively wait for the end of the execution task. If you need to wait until the end of the thread pool task execution, you need to call awaitTerminationActively wait for the end of the task call.

  • Wait until all submitted tasks (including running and waiting in the queue) are executed;
  • Wait until the timeout expires;
  • the thread is interrupted, throws InterruptedException;

If the execution of the thread pool task ends, awaitTerminationthe method will return true, otherwise it will return when the waiting time exceeds the specified time false.

// 关闭线程池的钩子函数
private static void shutdown(ExecutorService executorService) {
    
    
    // 第一步:使新任务无法提交
    executorService.shutdown();
    try {
    
    
        // 第二步:等待未完成任务结束
        if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
             // 第三步:取消当前执行的任务
            executorService.shutdownNow();
            // 第四步:等待任务取消的响应
            if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
                System.err.println("Thread pool did not terminate");
            }
        }
    } catch(InterruptedException ie) {
    
    
        // 第五步:出现异常后,重新取消当前执行的任务
        executorService.shutdownNow();
        Thread.currentThread().interrupt(); // 设置本线程中断状态
    }
}

other

I feel that the content is so much that I can’t finish it~!

When talking about the thread pool, we have to talk about multi-threaded concurrent operations, 同步, 异步, CSA, AQS, 公平锁和非公平锁, 可重入锁和非可重入锁and other knowledge points required for concurrency control.

It is rarely used in daily work. Do you have a systematic knowledge structure? It leads to forgetting after learning a lot, and then learning and forgetting again.

I hope that I will have the opportunity to learn and share step by step in the future.

The article is all told here, if you have other needs to communicate, you can leave a message~!

Guess you like

Origin blog.csdn.net/stven_king/article/details/113870331