线程池源码原理大畅享(全网独家)

线程池源码原理大畅享(全网唯一)

闲来无事,找工作有些难(不愿意海投,投了的四五家大部分都莫得了~~~),花一天时间复习线程池,发现网上没有好的博客,我直接来一篇从入门到精通的博客(我自己的角度),供伙伴们畅享。


线程池的概念,作用,用法这些的,就不多说了,这些的营养价值不是很高,网上很多这种博客。咱今天就带大家从头到尾走一遍线程走过的路,领略一些线程执行途中都经历了什么。

线程池的创建

提到线程池的创建,八股们肯定兴奋了,这个都熟。OK,我们就轻描淡写一下,尽量使用 new ThreadPoolExecutor(…) 来自己指定参数,参数有核心线程池大小,最大线程池大小,非核心线程非活跃状态保持时长,时长单位,阻塞队列,线程工厂,拒绝策略。

是的,这里非常简单,非常八股。源码也一样,把参数简单接收一下:

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;
}

这里很简单,那咱就穿插一部分比较有意思的东西,在这里顺便看看线程池一个巧妙的设计,线程池状态和线程数的设计。

源码先上(先看看,理解不来就往下看):

// 非常巧妙的变量ctl,咱马上细说
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
// 掩码(没听过的可以类比计算机网络子网掩码)
private static final int COUNT_MASK = (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;

// 利用掩码取状态(线程池状态和线程数)
private static int runStateOf(int c)     { return c & ~COUNT_MASK; }
private static int workerCountOf(int c)  { return c & COUNT_MASK; }
// 更新状态
private static int ctlOf(int rs, int wc) { return rs | wc; }

最有意思的设计之一:ctl

通过上面源码,大部分应该能看懂,咱开始细谈这个ctl变量。

咱先看一下我怕上面标注的线程池五种状态(不是转移话题,咱设计的系统都是高内聚低耦合的,写的文章也一样,这儿看一下这个比较容易理解),他们都是一个 int 值移动了一个 COUNT_BITS 位,而 COUNT_BITS 是 int 的位数(32 = 4 * 8)减 3 ,也就是都左移了 29 位。其实了解底层的伙计们,看了上面源码里说到的掩码概念,马上就能联想到,这玩意不单单是要把它扩大,可能有别的东西要存。完全正确。这里设计的精髓就在这儿,他前三位表示什么就不多说了,就是线程五种状态嘛(状态每一位对应一个 3 bit 的数字),后 29 位用来表示什么,赞从上面源码的 workerCountOf(int c) 方法名字也能看出来,就是表示工作线程数量。合起来,就是我们所见到的 ctl 字段。

现在也就能理解上述的所有代码的意义在哪里了吧?下面的掩码获取状态也就是计算机设计常用的,将需要的数的位数设为 1,作为掩码,和变量与操作,就能得到最终要的东西。

OK,一系列问题来了,这么做的意义又在哪里呢?为了省空间吗?其实吧,再弄一个数字来表示状态,其实也不大,用 byte 来表示就好了,一字节就能完成(实在是计算机不让小于一字节访问,要不恨不得只拿出三位来使用)。这么做其实就是为了能够在高并发的环境下,保证两个变量的值想对应,提供一起变的途径,也提供通过变量获取两个状态的方法。OK,下一个问题,这个设计,线程数量能够吗?这个我们可以结合官方给的解释来看,上图:

在这里插入图片描述

我这是中文插件,你们要是觉得英文更规范,请大佬你自己去看哈,我这英语界的“大牛”就勉强看中文了哈。他说,现在 29 位就可以使用约 5 亿个线程。我估计吧,现在的公司应该还没有需要突破这么多线程的业务吧,再者说,我估计现在的计算机应该都没办法在这么多线程的前提下发挥CPU的最大效率吧。而且他说了,以后要是不够了,就修改为 long 类型,再修改掩码位数就好。这是什么,格局,人家设计者考虑的不止现在,还有以后的以后~~~ 在渣男时代里也是一股情流了~ ~ ~

OK,下一个问题,这么设计,每次都要用掩码与操作一次,才能获取状态,这对CPU不是麻烦吗?好,这问题问出来就说嘛你小子不一般,格局在这儿,能考虑这么多。但是我觉得吧,或操作是最基本的操作之一,通过与非门加非门就能完成,而且吧,现在CPU几十级的流水,主频也超级快,相当于一频的事情,不是多大的负担,而且吧,咱现在冯诺依曼结构的计算机,CPU运算器的速度肯定不是瓶颈,IO才是关键,所以吧,影响我个人认为不是很大。最重要的一条,在并发量高的场景下,数据一致性、安全永远是第一重要的,因为这个,牺牲一小丢丢可以忽略的速度,完全可以接受嘛。

线程池的五种状态可以看上面图片中的提示,其实很简单,没必要马上记住,先简单看一下哈(一定要看一遍)

  1. RUNNING 是正常态

  2. SHUTDOWN 是开始终止,我们调用了 shutdown() 以后就会开始变为SHUTDOWN,这个状态不接受任务了,只会把已经接受的处理完

  3. STOP 和 SHUTDOWN 类似,调用 shutdownNow()会开始变为该状态。这种状态不接受任务,也不再处理任务,正在执行的任务还要中断。shutdownNow()方法还会将没用开始执行的线程返回。

  4. TIDYING 表示所有任务都终止了,SHUTDOWN 、STOP 状态下的线程都执行完了,阻塞队列也都空了,就会到该状态。

  5. TERMINATED 表示线程池彻底结束了。上面都是过程态。TIDYING 状态下调用 terminated() 方法就到了这个状态了。

觉得有点抽象正常,咱先往下看,下面我会再说一次,到那个时候咱就能轻松理解了。

线程走的路

接下来的就是咱的重点,哥们带你走一趟线程走过的路,让你看看又多艰辛。

提交线程有几种办法?八股肯定兴奋了,四种。其实提交任务最基本的方法是两种,实现Callable接口或者Runable接口,重写里面的 run() 方法和 call() 方法。往线程提交几种方法?八股们又兴奋了,submit(…) 和 execute(…),他俩有啥区别?这好说,一个只能交Runable,一个都能,一个能抛异常,一个只能在get()时抛出,一个有返回值,一个没有;剩下深层次的咱聊过源码以后大家就通透了。OK,我们从这里带大家走一次线程走的路。(说一句后话,其实 submit(…) 也是调用 execute(…),所以咱从submit(…)带大家走。)

submit(…)方法

以submit(…) 为例,它 Callable、Runable 都能提交,我们点击进去看源码(定位到 AbstractExecutorService 类中的方法,ThreadPoolExecutor没有重写),老规矩,先上源码:

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

// Runnable 的提交
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

我们从源码中能看到,他俩兄弟其实都是被封装成了 RunnableFuture 类,然后去执行的 execute(…) 方法。先看 RunableFuture 接口,能发现他是继承于 Runable 接口和 Future 接口。是啊,多老实,长得是啥本质就是啥,就是那两个类的子类(你要是问我为什么他能继承两个,那可能你需要重新看一下接口基础,大概可能因为Java是大部分C++写的吧)。也就是说,他返回了一个 Runable、一个 Future。咱再看这个 newTaskFor(task),上源码:

// Callable
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

// Runnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

一看,发现返回的是一个 FutureTask 类,这个哥们都熟呀,背过的对吧,再稍微一分析,其实这个 FutureTask 不就是 RunableFuture 的实现类嘛,也就是说,最上面传入execute(…) 的就是这个方法啊。再往深看源码:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

// 下面都是针对Runable,用到了适配器,看不懂代码的下面有说明
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

// 适配器内部类,主要看构造方法和 call() 方法。
private static final class RunnableAdapter<T> implements Callable<T> {
    private final Runnable task;
    private final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
    public String toString() {
        return super.toString() + "[Wrapped task = " + task + "]";
    }
}

现在能发现,其实他就是把 FutureTask 当成代理适配器(我个人觉得既有适配器的功能,又有代理的功能,没必要这么严格啦,能活用就好啦),里面真实对象其实是 callable 这个属性,这里面放了我们传入的 callable。那么问题又来了,Runnable 又怎么办呢,好说,最终要实现的目标都一样,都是调用其中的重写方法,那还不好说,来个适配器不就好了,弄一个对象适配器,实现 Callable接口,把 Runnable 的引用保存在自己身上,调用 call() 方法时,调用真实对象(Runable) 的 run() 方法,源码就是这么实现的。

不懂适配器的话,我们做个类比:就像你想养一只熊猫,你的想法有点刑,但又十分渴望。于是你找到了我,我为了你的W,便把一只很聪明的小狗,穿了一件智能外套,狗叫一声,我设计的外套把声音收集起来,变成熊猫的声音,用喇叭放出去,轻松解决了。

这个例子有点牵强了,但现在就能想到这么多,设计模式用多了就懂了

接下来,就是调用 execute(ftask) 把咱刚刚封装好的 Runnable (也就是FutureTask) 传进去执行,执行结束后返回刚刚封装好的 ftask 变量。、

这里我们稍微停一下

我带大家回顾一下,为什么返回 ftask 变量就是返回返回值呢?execute(ftask) 认为我们传入的 ftask 是一个Runable,最后执行的是其中的 run()方法,那么咱封装的 FutureTask 的run方法是什么,他只是一个适配器,run()方法只是调用自己 callable 的 call() 方法,结果保存到自身的 outcome 属性中。不懂?上代码:

public void run() {
    if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                // 保存结果,方法 code 在下面
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

protected void set(V v) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        outcome = v;
        STATE.setRelease(this, NORMAL); // final state
        finishCompletion();
    }
}

多嘴提一句,那咱刚开始传入的是 Runable 又咋办,其实一样嘛,调用适配器的 call() 方法,适配器调用真实 Runable 的 run() 方法。至于 result 的话,其实就是返回咱刚刚方法中,参数明显传入的。一般咱不会传,所以一般就是返回值 null 嘛。

理解了吗,是不是被绕晕了?没事,绕晕的再回去看一看,贴心的我给大家画了简单的UML(不规范的话,不要说哈,悄悄告诉我,毕竟我也是很爱面子的) ,大家结合着看更好理解一些。

没绕晕的,也再看看下面的UML,然后再问自己一次,理解了没有,没问题咱就接着往下走。

下面的Worker先不看,一会儿再看(聪明的兄弟应该从Worker这儿发现,到现在好像困难还没有结束,还有好多有营养的在后面等着咱呢)

在这里插入图片描述

execute(…)方法

到这儿的兄弟,咱可都是聪明人哈,那就以聪明人的方式相处,一间三连。哈哈,开个玩笑,咱开始往下分析execute(…) 方法

先看一下源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    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);
}

这段代码营养价值倒是不错,但是吧,能看到这儿的伙计们应该都会,这就是我们常常说的线程池的执行策略,而且这段代码在网上一搜都是,咱就不重复造轮子了,来一起背一下吧:优先使用核心线程,核心线程使用结束后,加入阻塞队列,阻塞队列满了以后,使用非核心线程运行,非核心线程池满了,使用拒绝策略拒绝。OK,我们到这儿讲完了~~~

???真讲完了???

哈哈,开个玩笑,大部分代码都是上面说的意思,比较简单稍微一看就懂,那么咱都是聪明人,咱看点难的。

addWorker(…)

上面的代码比较难一点的,就是这个 addWorker(…) 方法了,其余 workerCountOf()之类,能坚持到这里的,应该都懂,不懂的就好好想一想,想不通的就再私信问我。咱现在就开始addWorker(…)

咱还是先大致浏览一次代码,把解释咱直接写到代码里面:

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (int c = ctl.get();;) {
        // 检查,当前状态shutdown以上的情况下:
        // stop以上 或 接受的任务不为空 或 阻塞队列是空
        // 就直接返回 false,添加失败
        // 咱上面说过,SHUTDOWN 还是能处理队列的
        if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;

        for (;;) {
            // 目前线程数大于线程池最大数量,返回失败
            // 至于这里为什么还会有这个问题,我们暂且先放一放,后面就会有答案
            if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            // 元子类CAS增加数量,增加失败就重新循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            // 再次检查线程池状态
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // new 出新的 Worker,并获取其 thread 任务,
        // 这个我们下面细谈,不是很理解先跳过,大致浏览一下代码就好
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // workers锁,要保证workers安全,全局唯一
            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 c = ctl.get();
                // 检查线程池状态和线程状态
                if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // 加入workers
                    workers.add(w);
                    // 设置标志位
                    workerAdded = true;
                    // 更新大小
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            // 将 thread 进行 start()
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

上面的代码看完详细解释,一定还是“听君一席话,如听一席话”的感觉。要完全理解上述代码的实现,其实我认为关键点在于 Worker 类的理解,那接下来咱就看看这个Worker类是个啥玩意。

Worker类

首先我们看看Worker的标识

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable

我们可以看到,这 Worker 也是一个Runnable,唉,烦死了,咋全是 Runnable 啊,你说不能让他们直接用吗,转来转去。哈哈,其实这才是工程师的精妙之处,不深入研究的话确实会有这种想法。没关系,我们继续往下读,就会发现,其实这样设计是精美绝伦的。

接下里我们看看 Worker 的关键的属性和构造方法

final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

我们回忆一下,我们调用 new Worker() 的时候,传入的是啥来着?就是我们调用addWorker(…) 传入的那个 Runnable。我再给你出个难题,addWorker(…) 传入的这个 Runnable 哪里来的?哈哈,回想一下,execute(Runnable command) 的时候传入的,那么打破砂锅问到底,这个 Runnable command 又是哪里来的?AbstractExecutorService 的 submit(…) 的时候,RunnableFuture 的实现类 FutureTask 对吧,他里面有 callable 属性,引用的是我们传入的 Callable 或者 Runable,对吧。好的,都回忆起来了哈,那接下来就好说了。这个传入的 Runnable firstTask 的引用留在 firstTask 属性中,thread 是以这个 Worker 对象为 Runnable 而创建的 Thread 对象,现在知道咱创建线程池七大参数里面的线程工厂是干什么用的了吧,很多面试官都不问这个玩意,为啥,会不会他们也不会呢?哈哈,面试官远离八股文其实很久了,他们不记得这些细节也正常,但你说出来,不就是你的加分项了吗,记下来,下次面试给面试官一个冲击波。刚刚不是刚说了,Worker 也是一个 Runable 对吧。好,保存好现在的记忆,最好留个快照,乘这股子劲,不要多想,我们马上分析下面的

返回到上面 addWorker(Runnable firstTask, boolean core) 代码中,我们看到 new 出 Worker 以后,拿出了他的 thread 类,对吧,进行一系类标志位的设置以后,调用了 t.start(),这个t就是Worker 属性中的 thread 属性嘛。我们想一下,这个 t.start() 调用的是什么呢?Thread 类其实就是 Runable 的代理类嘛,他调用的主要逻辑其实还是 Runable 里面的 run() 方法对吧。那我们看看这里的 start() 调用的是什么?不就是我们创建 Thread 类中传入的 Runable 嘛,Runable 是什么,不就是我们 Worker 本身嘛。OK,到这里,咱再加载一下刚刚我们保存的快照,返回去再理一次上面的逻辑,再接着往下看。通过 t.start()调用 worker 对象的 run() 方法,那我们跑进 worker 的 run() 看一下。

public void run() {
    runWorker(this);
}

多简单,是吧,咱就是喜欢读这种长度的代码,但是吧,他好像调用了别的方法,行,看在他表现这么好,咱就看看他调用了 runWorker(…) 方法。(先大致看一下)

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 ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                // 前置空方法,不理解咱一会儿说
                beforeExecute(wt, task);
                try {
                    // 执行任务的run()方法
                    task.run();
                    // 后置空方法
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 结束处理
        processWorkerExit(w, completedAbruptly);
    }
}

呃(⊙o⊙)…早知道就不看了是吧。人生就是这样,有些东西投机取巧是没用的,出来混迟早都是要还的,哈哈,刚刚尝了甜头,马上就会有苦的东西到来,所以做事情一定要耐下心来,一步一步脚踏实地,不以甜喜,不以苦悲。好了,装 X 结束,哈哈,开始继续分析 runWorker(…)方法。

一个一个来看,咱先看 beforeExecute(wt, task)、afterExecute(task, ex);这对空方法。点进去看,啥也没有,就是空的。是不是大大的问号,为啥要这样设计。其实,Java类的设计有两种,一种是 final 修饰的,不可继承的,一种是 不由 final 修饰的,可以交给子类拓展的。知道这种设计理念,你也就是大佬了,格局打开,人家在做人家的事情的时候,就已经想到了别人会用人家的东西来做什么,甚至以后要朝哪个方向发展。实际上这种设计理念在很多项目中常见,Spring源码中,有好多都是预留下来给子类来拓展的,不信?ApplicationContext 加载过程中,我们熟悉的 refresh() 方法中,就有postProcessBeanFactory(beanFactory)、onRefresh()两个大方法交给子类来实现了,注册 BeanDefinitions 的时候,分析每个 xml 标签前,都要执行前后空方法:

preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

现在发现咱比人家开源项目作者的差距了吗?不是咱上咱也行,至少现在不行,你在看我的博客,说明你暂时也不行,哈哈,不过五年以后,十年以后,咱行不行谁知道呢,至少咱现在已经学会了他们的一项绝招了,哈哈,回到线程池代码中。

其实最最核心的就是 task.run() 方法,再次回想一下,这个 run() 方法是调用的谁的?worker 中传入的 Runable ,继续追问自己到底哈,我这儿就不再回答了,一遍一遍追问到底,到这儿应该也就熟悉很多了,等看完文章,你不就记住了?

好的,切记都回想一次再继续往下看哈,文章就在这儿,跑不了。接下来咱看一个另外的点,任务结束处理:

在想这个问题以前,兄弟们估计会想,线程池不是为了保存线程资源,避免来回切换吗?这里怎么看到就是新建worker,用线程工厂创建新线程,都没有提到怎么复用。另外,不是说了看了文章就精通线程池了吗,那我们创建线程池传入的过期时间参数又怎么运行的呢?难道内部有定时器吗?非也。兄弟能问出这种问题,那就是哥们的同志了,不得三连一下认识一下?能问出这种问题,就说明你没有忘记初心,能一直保留着自己一开始的问题,或者你能在局部的工作中,始终明白自己最终的方向。回归正题,我们的疑问都在接下来隆重登场的 processWorkerExit(w, completedAbruptly) 方法中得以解决。

processWorkerExit(w, completedAbruptly)

先大致看一下代码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // completedAbruptly 是我们传入的标志位,表示是否非正常结束
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 增加完成数量,删除正在运行 worker 集合
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 查看线程池状态,是否需要停止线程(不太理解没关系,咱一会儿说)
    tryTerminate();

    int c = ctl.get();
    // 在 running 状态或者 shutdown 状态,给 worker 继续找活儿干
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // 线程池的至少数量
            // 允许线程过期,就是0,否则就是核心线程数
            // 这里不懂没关系,其实这个allowCoreThreadTimeOut参数
            // 和我上面跟你们说的那个设计思路一样,默认是false,
            // 子类重写的话,是可以修改的,也就是核心线程池也是可以过期的
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 队列非空,核心线程也可以过期,那就把最小线程数加一,
            // 至少这个 worker 需要去处理队列里的任务
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 维持线程数量至少要到min,要是大于的话,这个线程就可以结束提前返回了
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 再次调用 addWorker ,只不过传入的 Runable 为null
        addWorker(null, false);
    }
}

看完注释大概就懂了,这个不是很难。先不急着解决我们的疑问,我们再看一下 addWorker(null, false) 中,Runable 为 null会出现啥玩意儿,兄弟们可以翻上去看一下,只要线程池状态还是RUNNING,就可以正常走到 t.start()。OK,这里又来了,咱又可以好好看一看回想一下,这几个Runable是怎么传递的了。不要嫌累,好好回想一下哈,哥们都看到这儿了,加把劲。 t.start()其实调用的是 worker 中的 run方法,你们记得自己再回想一次这几个 Runable 的传递哈,我这边就在这里继续往下说了, worker 中的 run() 调用自身的 runWorker(Worker w) 方法,我们翻上去再看这个方法,当 worker 的 firstTask 属性为null时,他会 task = getTask() 一下,那咱进去看看这个getTask() 是怎么样一个逻辑呢:

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

    for (;;) {
        int c = ctl.get();
        // 依旧检查状态
        if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        // 获取线程数量(记得怎么获取吗?ctl和掩码相与哈)
        int wc = workerCountOf(c);

        // 是否需要超时计时,核心线程参与计时或线程数量大于核心线程数
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 判断 是否允许超时且已经超时,且线程不是最后一个线程或阻塞队列是空
        // 其实就是判断,配置是否允许退出,环境是否达到退出标准
        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完成分发的任务以后,不准休息,监听阻塞队列,一旦来了任务就要马上获取然后执行。而非核心线程,worker 好比临时工,规定时间到了还没有任务,他就可以回家休息了。所以说,打工人,难啊,临时工想转正,转正了的又需要永无止境地干活,呜呜呜~~~纸巾递上。

到这儿,我问大家一个问题,非核心线程销毁,是只有阻塞队列满了以后加入的非核心线程才销毁吗?想一下再往后看,其实不一定,阻塞队列满了以后加入的线程和正常线程一样,谁也没办法区别对待,都是一视同仁,只不过,谁先完成,然后指定时间内拿不到任务,就需要销毁了,最终线程池只是能够保持的核心线程数量是规定的就好。此处有联想到我们打工人(我是预备打工人),又要哭一顿了。

那么这儿就解决了我们一直存在的那个疑问了,为什么咱明明在 addWorker()外面,execute() 那里就已经控制好了线程池的数量,为什么加入后还要判断数量有没有超,其实这就是答案了,有可能有些 worker 执行任务结束以后没有退出,又在阻塞队列里面拿到了任务,于是,就有了判断线程池数量有没有超过最大限值。

看到这里,你就已经是大佬了,我们最后浅浅地分析一波线程池的 shutdown() 方法;

shutdown()

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 验证权限
        checkShutdownAccess();
        // 原子更新状态为 SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 给 worker 依次设置中断位
        interruptIdleWorkers();
        // 留给子类来实现
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试执行 Terminate()方法,以变成 Terminate 状态
    tryTerminate();
}

问题来了,为什么要执行 Terminate() 方法呢?销毁不就好了吗?这个地方我认为他是一个空实现,交给子类来拓展的,毕竟线程池这么大个东西,真正停止之前有大概率可能会伴随着设置某些标志位,或者回收某些资源,ThreadPoolExecutor 可能没用到,子类很可能就用到了对吧。

到现在,最后回顾一次线程五种状态,正常的 Running、调用 shutdown() 以后到达的 SHUTDOWN 状态,这个状态不接受任务,只处理已经存在的任务、RUNNING状态下调用 shutdownNow() 方法到达的 stop,不再接受任务,中断正进行的任务,返回未开始执行的任务、STOP 和 SHUTDOWN 状态下,线程池和阻塞队列全空以后到达的 TIDYING 状态,执行 Terminate()方法后变成的 Terminate 状态。

然后再回顾一次 Runable 的传递封装过程:…

好了,到现在线程池知识点基本上就结束了,读到这里的兄弟们,你现在可以拍着胸脯对我说,你精通线程池了(至少我提问问不住你了,哈哈,还是不要往人家大佬枪口上撞为好)。

以上内容呢,都是自己看代码分析出来的,哥们学习只是只相信代码,别人说的不一定认同,所以以上观点有很强的主观性,要是跟大伙的认知有冲突或者出入的话,咱交流。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值