Thread pool ThreadPoolExecutor source code analysis

Thread pool ThreadPoolExecutor source code analysis

Overview

  1. How threads are created
  2. Thread pool parameters
  3. Thread pool execution flow chart
  4. Execution process source code analysis
  5. Thread pool status diagram
  6. worker encapsulation
  7. Subsequent processing of thread execution
  8. The overall workflow of thread execution tasks in the thread pool

1. How to create threads

  1. Inherit the Thread class

Insert image description here

  1. Implement runnable interface

    [External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-UrdyouiC-1691579008075) (C:\Users\hejh\AppData\Roaming\Typora\typora-user-images\ image-20230809160842904.png)]

  2. Implement the callable interface and receive return values

    [External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-l497CyhI-1691579008077) (C:\Users\hejh\AppData\Roaming\Typora\typora-user-images\ image-20230809161233187.png)]

  3. Customize the thread pool or use the thread pool that has been written under the JUC package: The thread pool method is actually no different from the above, except that the above requires frequent creation and destruction of threads, which will cause some unnecessary additional resource consumption, so in actual development The thread pool method will definitely be used in Java. Executors in Java come with some methods of creating thread pools.

    Under the Executors class:

    [External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-QLJADNcf-1691579008078) (C:\Users\hejh\AppData\Roaming\Typora\typora-user-images\ image-20230809164946394.png)]

2. Thread pool parameters

java.util.concurrent.ThreadPoolExecutor is a class for custom thread pool:

 /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize, //核心线程数
                              int maximumPoolSize, //最大线程数
                              long keepAliveTime, //空闲线程存活时间
                              TimeUnit unit, //时间单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory, //线程工厂
                              RejectedExecutionHandler handler) // 拒绝策略
  • corePoolSize thread pool core thread size will maintain a minimum number of threads in the thread pool. Even if these threads handle the idle state, they will not be destroyed unless allowCoreThreadTimeOut is set. The minimum number of threads here is corePoolSize.

  • maximumPoolSize The maximum number of threads in the thread pool. After a task is submitted to the thread pool, it will first find out if there are any idle surviving threads. If there are, the task will be directly handed over to the idle thread for execution. If not, it will be cached in the work queue (will be introduced later) ), if the work queue is full, a new thread will be created, then a task will be taken out from the head of the work queue and handed over to the new thread for processing, and the newly submitted task will be placed at the tail of the work queue. The thread pool will not create new threads without limit. It will have a limit on the maximum number of threads, which is specified by maximumPoolSize.

  • keepAliveTime idle thread survival time. If a thread is idle and the current number of threads is greater than corePoolSize, then the idle thread will be destroyed after the specified time. The specified time here is set by keepAliveTime.

  • unit unit of measurement of idle thread survival time unit keepAliveTime

  • After a new task in the workQueue work queue is submitted, it will first enter the work queue, and then the task will be taken out of the queue during task scheduling. There are four work queues provided in jdk:

    1. ArrayBlockingQueue is an array-based bounded blocking queue, sorted by FIFO. After a new task comes in, it will be placed at the end of the queue. The bounded array can prevent resource exhaustion problems. When the number of threads in the thread pool reaches corePoolSize and a new task comes in, the task will be placed at the end of the queue, waiting to be scheduled. If the queue is already full, a new thread is created. If the number of threads has reached maxPoolSize, the rejection policy will be executed.

    2. LinkedBlockingQueue is an unbounded blocking queue based on a linked list (in fact, the maximum capacity is Interger.MAX), sorted according to FIFO. Due to the approximately unbounded nature of the queue, when the number of threads in the thread pool reaches corePoolSize, new tasks will come in and will be stored in the queue without creating new threads until maxPoolSize. Therefore, when using this work queue, the parameter maxPoolSize Actually it doesn't work.

    3. SynchronousQueue is a blocking queue that does not cache tasks. The producer puts a task in and must wait until the consumer takes out the task. That is to say, when a new task comes in, it will not be cached, but will be directly scheduled to execute the task. If there are no available threads, a new thread will be created. If the number of threads reaches maxPoolSize, a rejection policy will be executed. 4. PriorityBlockingQueue is an unbounded blocking queue with priority. The priority is implemented through the parameter Comparator.

  • The threadFactory thread factory is used when creating a new thread. It can be used to set the thread name, whether it is a daemon thread, etc.

  • handler rejection policy When the tasks in the work queue have reached the maximum limit, and the number of threads in the thread pool has also reached the maximum limit, how to handle if a new task is submitted. The rejection strategy here is to solve this problem. JDK provides 4 rejection strategies:

[External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-rJeEfDbi-1691579008079) (C:\Users\hejh\AppData\Roaming\Typora\typora-user-images\ image-20230809165906985.png)]

3. Thread pool execution flow chart

4. Execution process source code analysis

PS: The following methods are all under the java.util.concurrent.ThreadPoolExecutor class

1. Properties

    //原子类,用于保存线程池的状态和工作线程的数量。总共32位,前3位表示线程池的状态,后29位表示工作线程的数量 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 线程池的状态,保存在前三位
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // ctl的构造和 拆分其得到线程池的状态和工作线程的数量
    private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

2. execute method

public void execute(Runnable command) {
    
    
        //健壮性判断
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果工作线程的数量小于核心线程数,则创建线程处理任务,addWorker之前都要检查线程池状态  
        if (workerCountOf(c) < corePoolSize) {
    
    
            if (addWorker(command, true))
                return;
            //防止多线程情况下c变成其他值
            c = ctl.get();
        }
        //核心线程已满,如果线程池处于运行状态,将任务放进阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            int recheck = ctl.get();
            //重复检查,如果不是运行状态,移除队列中的任务,拒绝任务。addWorker之前都要检查线程池状态
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果工作线程为0,就添加一个线程,避免出现队列任务没有线程执行的情况。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
       //添加线程处理这个任务,如果失败则拒绝。
        else if (!addWorker(command, false))
            reject(command);
    }

3. addWorker method

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
        retry:
        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);

            // 仅在必要时检查队列是否为空。
            if (rs >= SHUTDOWN && // 线程池属于关闭状态,无法接受新任务,也无法处理池中任务,
                ! (rs == SHUTDOWN && // !SHUTDOWN,即是STOP,TIDYING,TERMINATED
                   firstTask == null && // firstTask不为空 -> 这里对应上述的addWorker(null,false)
                   ! workQueue.isEmpty()))// 工作队列为空
                return false;

            for (;;) {
    
    
                int wc = workerCountOf(c);
                //工作线程数再判断
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //通过CAS操作增加工作线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry; 
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // CAS 由于workerCount 变化而失败;重试内循环
            }
        }
        //开始添加工作线程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
    
    
            w = new Worker(firstTask);
            final Thread t = w.thread;
            //判断t!=null的目的,防止t创建失败
            if (t != null) {
    
    
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();//加锁的原因就是largestPoolSize,
                try {
    
    
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||//线程池运行中
                        (rs == SHUTDOWN && firstTask == null)) {
    
    //处于shutdown状态,firstTask == null这里对应上述的addWorker(null,false)
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);//workers:线程池中所有工作线程的集合,
                        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;
    }

4. addWorkerFailed method, add thread failure method

//通过上面的代码,总结哪些情况会出现添加失败的情况:
1,线程池不在运行状态
2,线程已经启动    
这2种情况都没有添加成功    workers.remove(w);不会有问题。
private void addWorkerFailed(Worker w) {
    
    
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//因为decrementWorkerCount而加锁
        try {
    
    
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            //重新检查线程池终止情况,以防此线程存在影响线程池终止。
            tryTerminate();
        } finally {
    
    
            mainLock.unlock();
        }
    }

5. Thread pool status diagram

[External link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-SnaGuPtK-1691579008080) (C:\Users\hejh\AppData\Roaming\Typora\typora-user-images\ image-20230809180334809.png)]

6. Worker packaging

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {
    
    
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        Worker(Runnable firstTask) {
    
    
            setState(-1); // 添加标识,worker运行前,禁止中断(AQS)
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** 具体运行调用外部(threadPoolExecutor)的方法  */
        public void run() {
    
    
            runWorker(this);
        }

        // Lock methods AQS的状态
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
    
    
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
    
    
            if (compareAndSetState(0, 1)) {
    
    
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
    
    
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        {
    
     acquire(1); }
        public boolean tryLock()  {
    
     return tryAcquire(1); }
        public void unlock()      {
    
     release(1); }
        public boolean isLocked() {
    
     return isHeldExclusively(); }

        void interruptIfStarted() {
    
    
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                }
            }
        }
    }

java.util.concurrent.ThreadPoolExecutor#runWorker method

final void runWorker(Worker w) {
    
    
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
    
    
            //任务不为null,就一直循环,否则调用getTask尝试从阻塞队列获取任务
            while (task != null || (task = getTask()) != null) {
    
    
                w.lock();// 加锁的目的是表示当前任务正在执行,你shutdown任务也不会中断
                // 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);
        }
    }

getTask method:

 private Runnable getTask() {
    
    
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //
            if ((wc > maximumPoolSize || (timed && timedOut))//当前工作线程数大于最大线程数 ,后面判断表示是否是允许核心线程超时并且真的超时
                && (wc > 1 || workQueue.isEmpty())) {
    
    //工作线程 > 1或者 阻塞队列为空
                if (compareAndDecrementWorkerCount(c))// 干掉当前工作线程并返回null,CAS的方式,如果失败,重新走一遍
                    return null;
                continue;
            }

            try {
    
    
                Runnable r = timed ?
                    // 这里是可能出现超时情况并且允许回收线程,那就阻塞这么久拿阻塞队列的任务
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                //这里是线程阻塞在这,等待任务,不参与回收的情况,直到触发signal方法被唤醒,走catch继续下次循环
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }

7. Subsequent processing of thread execution

processWorkerExit method:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
        if (completedAbruptly) // 如果停止,减一个工作线程数
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//加锁为了移除工作线程workers.remove(w);
        try {
    
    
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
    
    
            mainLock.unlock();
        }

        tryTerminate();// 尝试干掉线程池

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
    
    
            // 如果不是认为停止,需要判断线程是否需要追加一个线程处理任务
            if (!completedAbruptly) {
    
    
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;// 查看核心线程是否允许超时
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;// 如果允许超时,并且工作队列不是空,就将min设置为1
                if (workerCountOf(c) >= min)// 如果工作线程数量大于核心线程数,就直接结束
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

8. The overall workflow of thread execution tasks in the thread pool

Insert image description here

Rough process description:

  1. When executing a task, if the number of core threads does not reach the corePoolSize value, a thread will be created. Otherwise, it is put into the task queue.
  2. When the task queue is full, threads are created, but the total number of threads cannot exceed the maximumPoolSize value.
  3. If the task queue is full and the number of threads reaches the maximumPoolSize value, the failure policy will be executed.
  4. The worker thread continuously polls the poll tasks in the queue. If poll is empty, the worker thread execution ends (recycling thread).
  5. If the number of worker threads <= the number of core threads corePoolSize, use take to get tasks from the queue (core threads always await).

First of all, getTask is in an infinite for loop. It is judged that the number of currently running threads <= the number of core threads, and the take method of the blocking queue is directly called. It blocks indefinitely until data is put in the queue, and wakes up to exit the for loop.

If the number of currently running threads > the number of core threads, if there is no data in the queue, it will block keepAliveTime before returning. When continuing the for loop, if the queue is empty, it will exit for and destroy the thread blocked on the queue.

Thread pool process summary

When the task is submitted, if the number of currently running threads is less than the number of core threads, construct a worker and call the worker's start to start a thread. The run method of the thread is a while loop. In the loop, getTask is called to retrieve data from the global blocking queue. The data is executed immediately after retrieval, and then blocked on the queue of the getTask method.

When the task is submitted, if the number of currently running threads is greater than or equal to the number of core threads and the queue is not full, it is put into the global blocking queue. At this time, the thread blocked by getTask above will wake up to execute the business task.

When the task is submitted, if the number of currently running threads is greater than or equal to the number of core threads and the queue is full, it will continue to determine whether the number of currently running threads exceeds the maximum number of threads. If it exceeds the maximum number of threads, the task rejection policy will be used, otherwise a task is constructed. worker, call the worker's start to start a thread, and perform the first step of the while loop getTask operation.

getTask operation

When getTask, if the number of currently running threads is within the number of core threads and the threads are not in tension, they will be blocked in the queue indefinitely until data is put into the queue and then return to execution. If the number of currently running threads is greater than the number of core threads, the thread will start There are more. The keepAliveTime will be blocked. If no data comes from the queue during this period, the worker thread will be destroyed and allowed to be recycled.

What is the role of KeepAliveTime?

keepAliveTime (thread activity retention time): The time that the thread pool's worker thread remains alive after it is idle. This parameter is only useful when the number of threads is greater than corePoolSize. Idle threads that exceed this time will be terminated; but if allowCoreThreadTimeOut is set to true, the core thread count will also be destroyed if there is no task execution until the keepAliveTime time. If there are many tasks and the execution time of each task is relatively short, you can increase this time to improve thread utilization.

Guess you like

Origin blog.csdn.net/Edward_hjh/article/details/132194867