【无标题】

Java 线程池原理

参考:https://juejin.cn/post/6986627892729872420

1 概述

线程池 的作用不用太说了,线程池会按照一定的规则,创建和维护一定数量的线程。这些线程可以被循环利用,来处理用户提交的任务。对比不同线程池的使用方式,节省了频繁的创建和销毁线程带来的性能开销。

2 概念理解

2.1 工作线程(worker)

指的是当先线程池用于处理任务的worker对象,每个worker对象内部持有一个thread对象。

2.2 任务

调用方,要执行的业务鹿皮,一半应该是callable或者Runnable的实现。

2.3 任务队列

线程池哪位维护了一个队列,用来存储待处理的任务,每个线程都可以从该队列获取任务进行处理。

2.4 核心线程数

线程池哪部需要维持的一个最小的数量的工作线程,工作线程数量不足这个数量的时候,新来的任务都会提交一个新的工作线程,任务不会放入 队列。工作线程数到达这个数量的时候,新来的的任务,都会放入这个队列中。所有的工作线程,都回去轮训这个任务队列。

2.5 线程池一共有5种工作状态。

running:正常运行的状态,此时,可以正常接受和处理任务。

shuntdown: 此时不可以接收新的任务,但是可以正常处理,但是不再接受新的任务,并且终端空闲的工作状态

stop: 此时清除队列中的没有处理的任务,中断所有的工作线程。

tidying: 从shuntdown 或者 stop 自动流转到这个状态。任务列表和工作线程,都为空。

terminated: 终止状态,从tidiying状态自动流转到此状态。此时队列中的任务为空,工作线程列表为空,并且已经执行完 terminated 回调函数。

三 代码阅读

// 所以这个 COUNT_BITS = 29,意思是存储工作线程数的二进制为占29位
 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/*
 CAPACITY 实际上就是2的29次方减1,具体的实际数值是多少不重要,重点是关注二进制表示:
    1左移29位实际上就是:0010 0000 0000 0000 0000 0000 0000 0000
    再减个1后得到的就是:0001 1111 1111 1111 1111 1111 1111 1111
 所以这个CAPACITY的有效位就是29个1
*/ 
  private static final int COUNT_BITS = Integer.SIZE - 3;
// runState is stored in the high-order bits
// 状态值存储在高(3)位
/*
-1的二进制表示:1111 1111 1111 1111 1111 1111 1111 1111 (补码标识法)
-1左移29位之后:111 00000 0000 0000 0000 0000 0000 0000 (左侧切断,右侧补0)
*/

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    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;

    // Packing and unpacking 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; }```



四 方法解析

4.1 execute 方法解析

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
        * Proceed in 3 steps:
        *
        * 1. If fewer than corePoolSize threads are running, try to
        * start a new thread with the given command as its first
        * task.  The call to addWorker atomically checks runState and
        * workerCount, and so prevents false alarms that would add
        * threads when it shouldn't, by returning false.
        *【这一部分注释对应第一个if块的内容】
        * 如果运行的线程数少于核心数量,尝试开启一个新的线程,并将提交的任务
        * 赋给这个新的线程执行,调用addWorker的时候原子化的校验运行状态和工作线程数量,
        * 如果不允许创建工作线程的时候会返回个false。
        * 
        * 2. If a task can be successfully queued, then we still need
        * to double-check whether we should have added a thread
        * (because existing ones died since last checking) or that
        * the pool shut down since entry into this method. So we
        * recheck state and if necessary roll back the enqueuing if
        * stopped, or start a new thread if there are none.
        * 【这一部分注释对应第二个if块的内容】
        * 如果一个任务能够被成功的加入队列,仍然需要再次校验是否还需要添加
        * 一个工作线程(因为在这段间隙中可能原有的工作线程有消亡的)或者任务添加
        * 进来后线程池被关闭。所以需要再次校验一下状态,以便于线程池被关闭的时候
        * 回滚入队操作;或者当没有可用工作线程时再创建一个。
        * 
        * 3. If we cannot queue task, then we try to add a new
        * thread.  If it fails, we know we are shut down or saturated
        * and so reject the task.
        * 【这一部分注释对应 else if块 的内容】
        * 如果任务不能入队,说明任务队列已满(接收不了新任务了),那么需要尝试创建一个新的工作线程。
        * 如果创建线程失败,我们可以推断出线程池已经关闭(不接收新任务了)。
        */
    int c = ctl.get(); // 获取控制标识
    /*
     从控制标识数值中拆分出来(二进制的后29位)工作线程数
     如果工作线程数小于设置的核心线程数,默认设置下(allowCoreThreadTimeOut为false),即使工作线程都闲着、没任务处理,也会继续创建新的工作线程,以达到维持最小核心工作线程数的目的。
     addWorker方法的作用就是创建工作线程,并把任务交给这个新建的工作线程去执行(command不会被放入任务队列)
     addWorker方法下面有单独的解析
    */ 
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) // 如果新的工作线程创建成功,则直接返回
            return;
        /*
         如果走到这一步 ,说明工作线程没有创建成功,
         可能由于并发请求情况下
            其他的请求已经新建了工作线程,本请求再去创建的时候,已经超过核心线程数阈值了,所以创建失败  
            也可能由于其他请求触发了线程池关闭动作,导致不可以再创建新的工作线程
         这个时候再重新获取一下控制标识,便于下面再检查一下线程池的状态和工作线程数
        */
        c = ctl.get();
    }

    // 走到这一步,说明工作线程没有创建成功,任务也没有提交成功 
    /*
     如果线程池处于运行状态,工作线程没有新建成功,那么说明是已经达到核心线程数了,所以直接把任务提交给工作队列,排队等着被执行就可以了
    */
    if (isRunning(c) && workQueue.offer(command)) {
        // 任务被成功添加到队列后,再获取一下控制标识
        int recheck = ctl.get();
        // 再check一下线程池状态,如果不是运行状态了,那么调用remove方法,从队列里删除掉这个任务
        if (! isRunning(recheck) && remove(command))
            reject(command); // 如果成功从任务队列里删除了任务,那么还要调用reject方法来回调一个拒绝任务的处理策略
        /*
         下面这个 else if 如果能够被执行到的话,线程池状态是正常的执行状态 或者 状态不是running但是remove失败
         无论是哪种可能,当前,待处理的任务还存在于队列中。
         那么检查一下工作线程数是否为0,如果为0,说明没人处理任务,那么就需要创建一个工作线程
        */    
        else if (workerCountOf(recheck) == 0) 
            /*
             第一个参数null,意味着没有传递任务,只是新建工作线程而已
             第二个参数false,意味着要创建的不是核心工作线程,那么校验的时候就校验工作线程总数是否超过设置的最大工作线程数
            */
            addWorker(null, false);
    }
    /*
     走到这个逻辑分支的话,说明线程池是关闭(非运行)状态  或者 任务队列满了 
     如果是任务队列满了 ,只需要创建一个工作线程,把任务交给这个线程去执行就可以了(任务不入队列)
     但是如果还是返回失败,就说明可能是:
        1.线程池的状态是关闭(非运行)状态了,不接受新任务了
        2.工作线程数已经达到了最大值,不能再新建了。
        3.工作线程创建或者启动失败。(可能性不大)
     只要任务提交失败,那么就需要调用reject方法来回调一个拒绝任务的处理策略
    */ 
    else if (!addWorker(command, false))
        reject(command);
}

4.2 addWorker 解析

/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked.  If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
* 
* 在当前的线程池状态和给定的边界控制逻辑的情况下,如果允许新建一个工作线程创建,
* 那么工作线程数会随之调整,随之创建一个新的工作线程并且启动它,并且把需要运行的任务交给它去运行。
* 如果线程池已经被停止了或者被合法的关闭,那么这个方法会直接返回false,也就不会创建新的线程。
* 如果创建新的工作线程是失败了(可能因为线程工厂返回了null或者抛出了异常),那么这个方法同样也会返回false。
* 
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
*
* -- firstTask 指的是要交给新建的工作线程运行的第一个任务(并不一定是线程池的第一个任务)
* 当工作线程数小于核心线程数的时候或者当工作队列满的时候,会创建一个新的工作线程,并运行该任务
* 
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
*
* core如果为true,则工作线程数的校验边界就是 设置的核心线程数,否则就是设置的最大线程数。
* 
* 
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
    retry: // goto作用的标号,下面一定要接一个循环
    for (;;) { // 这是个死循环,只能依赖内部的逻辑跳出,这种用法在concurrent的源码里很常见,主要都是为了循环检测状态
        int c = ctl.get(); // 获取控制标识
        int rs = runStateOf(c); // 获取当前线程池的状态

        /*
         如果 rs >= SHUTDOWN说明,线程池状态可能为SHUTDOWN、STOP、TIDYING、TERMINATED,
         无论哪个状态,线程池都是拒绝再接收新任务的。在这样的前提下:
            rs == SHUTDOWN : 说明正在关闭 ,还没有到终止状态
            firstTask == null:说明并没有提交任务,只是为了增加工作线程
            ! workQueue.isEmpty():队列不为空说明还有任务未消费,可以通过增加工作线程协助消费

            同时满足以上三个条件,也是可以继续往下执行新增工作线程的逻辑的,但是如果没有同时满足以上三个条件则 直接返回false
        */
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;

        // 走到这一步起码说明,线程池还是可以继续处理任务的
        for (;;) { // 又一个循环检测
            int wc = workerCountOf(c); // 获取worker(工作线程)数目
            /*
             如果 工作线程数 已经大于CAPACITY(2的29次方减1) ,这个值很大了,很少有计算机能够支持这么多的线程数,如果大于这个值就直接返回false
             如果core为true,说明调用方的目的是想创建核心工作线程,此时就要检测当前的工作线程数是否小于设置的核心线程数,如果大于这个值就直接返回false
             如果core为false,说明调用方的目的就是想单纯的新增工作线程,此时就要检测当前工作线程数是否小于设置的最大线程数,如果大于这个值就直接返回false
            */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;

            // 走到这一步,说明可以创建工作线程了,那么先把工作线程数递增(加1)  
            if (compareAndIncrementWorkerCount(c))
                break retry; // 如果工作线程数递增成功,则通过break retry 可以跳出最外层的for循环

            // 能够走到这一步,说明工作线程数递增失败,可能是由于其他线程的并发调用更改了工作线程数 或者 线程池状态发生了变更    
            c = ctl.get();  // Re-read ctl  重新获取一下控制标识 (在以上逻辑执行过程中,其他并发调用,可能会引起线程池状态变更)
            // 重新获取线程池状态 ,如果和之前获取的不一致,那么需要跳转到retry标号,重新执行一次外层循环的逻辑,这样就可以重新获取一次线程池状态
            if (runStateOf(c) != rs)  
                continue retry;

            // 如果不是因为线程池状态问题导致的工作线程数变更失败,那么就继续执行内部循环逻辑(自旋重试)  
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false; // 工作线程是否启动
    boolean workerAdded = false; // 工作线程是否添加
    Worker w = null; // 声明一个工作线程对象
    try {
        w = new Worker(firstTask); // 创建一个工作线程对象
        final Thread t = w.thread; // 获取工作线程对象内部用于执行任务的thread对象
        /*
         t什么时候为空?
         在执行Worker的构造函数时,就会实例化其内部的thread属性,实例化的方式,就是调用线程工厂的newThread方法。
         而线程工厂是个接口,是可以用户自定义实现类的(也有默认的实现),用户实现的newThread方法是有可能由于编码失误返回null的。
        */ 
        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.
                // 在持有锁的情况下重新check线程池状态,防止在创建worker阶段或者获取锁之前的逻辑执行时,状态发生变化。
                int rs = runStateOf(ctl.get());
                /*
                 rs < SHUTDOWN:说明是RUNNING状态,那么线程池是可以正常接收新提交的任务的
                 rs == SHUTDOWN && firstTask == null : 说明处于关闭状态,但是没有提交任务,这种情况线程池还是可以正常处理队列中现存的任务的。
                */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 检查一下worker对象中的线程对象是否已经执行了start方法
                    // 如果已经执行了,说明状态不对,抛出线程状态异常。   
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w); // 将worker对象w添加到工作线程集合
                    int s = workers.size(); // 获取工作线程集合大小
                    // 这个largestPoolSize是统计线程的生命周期内曾经达到过的工作线程数的最大值
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true; // 工作线程是否添加的标识置为true
                }
            } finally {
                mainLock.unlock(); // 释放锁
            }
            if (workerAdded) {  // 如果成功的添加了工作线程
                /*
                 启动工作线程
                 这个方法最终会执行Worker对象的run方法,内部又会调用runWorker方法。达到的效果就是,先处理firstTask,处理完之后再去队列中获取其他任务。
                */ 
                t.start(); 
                workerStarted = true;  // 工作线程是否启动的标识置为true
            }
        }
    } finally { 
        /*
         在finally里块中检测工作线程是否启动标识
         如果workerStarted为false (工作线程创建失败 或者 工作线程启动失败)
         需要调用addWorkerFailed方法进行一些回滚操作
            工作线程列表移除掉启动失败的工作线程
            工作线程计数递减
        */ 
        if (! workerStarted) 
            addWorkerFailed(w);
    }
    // 最后返回工作线程是否启动的标识
    return workerStarted; 
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值