拆解ThreadPoolExecutor之addWorker方法的各个细节?

author:编程界的小学生

date:2021/05/30

flag:不写垃圾没有营养的文章!

如果下面的问题你都会的话就别在这浪费时间啦

  • addWorker的流程知道吗?
  • addWorker里面为什么在add和remove的时候要上锁?
  • 提交的任务会包装成Worker对象对吧?Worker存到哪里了?
  • Worker添加失败或者添加成功但是线程启动失败会怎样?

一、全貌

private boolean addWorker(Runnable firstTask, boolean core) {
    1. 前戏:判断+CAS设置线程数+12. 开始:真正执行任务。
}

二、前戏

1、全貌

retry:
for (;;) {
    // 【1】
    int c = ctl.get();
    // 【2】
    int rs = runStateOf(c);
	// 【3】
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;
	// 【4】
    for (;;) {
        // 【5】
        int wc = workerCountOf(c);
        // 【6】
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
        // 【7】
        if (compareAndIncrementWorkerCount(c))
            break retry;
        // 【8】
        c = ctl.get();
        // 【9】
        if (runStateOf(c) != rs)
            continue retry;
    }
}

2、拆解【1】

// 老生常谈,获取ctl,ctl包含线程池状态和线程池当前活跃线程数。
int c = ctl.get();

3、拆解【2】

// 获取线程池状态。
int rs = runStateOf(c);

4、拆解【3】

if (rs >= SHUTDOWN &&
    ! (rs == SHUTDOWN &&
       firstTask == null &&
       ! workQueue.isEmpty()))
    return false;

我第一次看到后,觉得没啥,几个判断嘛,仔细梳理后,一脸懵逼,这都啥玩意,把自己绕进去了。最后还是绕出来了,总结一下:

  • 如果线程池不是RUNNING状态且不是SHUTDOWN状态,则直接return false。外层的execute不做处理或执行拒绝策略。
  • 如果线程池是SHUTDOWN状态,且此次有提交新的任务过来(最外层有取反操作),且任务队列是空,则return false,外层的execute不做处理或执行拒绝策略。

问题来了,什么叫此次有提交新的任务?

上一篇中execute方法里调用addWorker的地方有三个:

  1. 开启核心线程执行任务,addWorker(command, true);
  2. 核心线程满了,任务队列添加成功了,但是核心线程都超时了,导致线程池中线程数为0,addWorker(null, false);
  3. 核心线程满了,任务队列也满了。则开启非核心线程来执行任务,addWorker(command, false);

所以在execute中,第2步就叫没有提交新的任务过来,第1和3两步就叫有提交新任务过来,所以是在execute里控制的。

5、拆解【4】

// 就是一个cas,没啥说的。
for (;;) {...}

6、拆解【5】

// 每次都重新获取线程池中当前活跃线程数,因为Java是多线程的,所以需要重新获取。
int wc = workerCountOf(c);

7、拆解【6】

if (wc >= CAPACITY ||
    wc >= (core ? corePoolSize : maximumPoolSize))
    return false;

如果线程数超过了最大容量或者线程数超过了设置的线程数,则return,这个三目表达式秒啊,省了多少if else的代码。

8、拆解【7】

if (compareAndIncrementWorkerCount(c))
    break retry;

CAS的方式将线程数+1,也就是ctl变量的低29位。

9、拆解【8】

// 重新获取ctl
// 能走到这一步代表上一步没有break跳出循环,也就代表CAS失败了。
// 如果CAS设置失败,原因肯定是因为多个线程竞争导致的,所以重新获取线程池的状态,也就是需要重新获取ctl。
c = ctl.get();

10、拆解【9】

// 判断线程池状态是否发生变化,若没有变化则继续CAS。
// 继续的意思是因为最外层是for(;;),所以会继续下一轮循环,如果发生了变化,则continue最外层循环,重新获取。
if (runStateOf(c) != rs)
    continue retry;

11、小结

  • 判断线程池状态是否还能继续接收任务,比如如果是SHUTDOWN之后的状态,则肯定不允许接收任务。
  • CAS的方式给当前线程池中的线程数+1。

后半截就真正开始执行任务咯。

三、开始

1、全貌

// 【1】
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // 【2】
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        // 【3】
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 【4】
            int rs = runStateOf(ctl.get());
			// 【5】
            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive())
                    throw new IllegalThreadStateException();
                workers.add(w);
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        // 【6】
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    // 【7】
    if (! workerStarted)
        addWorkerFailed(w);
}

2、拆解【1】

// worker是否开始执行,也就是是否执行:t.start();
boolean workerStarted = false;
// worker是否被加到任务队列里(workers,HashSet)
boolean workerAdded = false;
// 任务对象
Worker w = null;

3、拆解【2】

// 将任务包装成Worker,任务对象。
w = new Worker(firstTask);
// 执行这个任务的线程。
final Thread t = w.thread;

题外篇:
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}  

Worker下一篇会详细分析,以及run方法是怎么个流程。

4、拆解【3】

// 上锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();

加锁目的是为了保证下面的workers.add(worker)方法在多线程操作时候是线程安全的。

5、拆解【4】

// 获取线程池的状态
int rs = runStateOf(ctl.get());

6、拆解【5】

// 【5】
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
    // 如果线程已经被start过了,则抛出异常,不允许重复调用start(下面的【6】会start)
    if (t.isAlive())
        throw new IllegalThreadStateException();
    // 添加任务到HashSet任务队列
    workers.add(w);
    int s = workers.size();
    // 如果workers的长度(任务队列长度)大于阈值,则更新阈值。
    if (s > largestPoolSize)
        largestPoolSize = s;
    // 设置为任务添加成功
    workerAdded = true;
}
} finally {
    // 解锁
    mainLock.unlock();
}

如果线程池的状态是RUNNING或者线程池状态是SHUTDOWN但是任务是null的话(execute第二步执行会是null,详细的可以看【前戏#拆解【3】】),则添加任务到workers,且标记workerAdded = true;代表任务添加成功。最后finally里解锁。

7、拆解【6】

if (workerAdded) {
    t.start();
    workerStarted = true;
}

如果任务添加成功了,那么OK,启动线程,最后标记线程启动成功!

8、拆解【7】

finally {
    // 【7】
    if (! workerStarted)
        addWorkerFailed(w);
}

如果添加任务的流程中失败了或者添加成功了,但是执行任务的线程启动失败了,则走失败的策略。那失败的策略到底是啥呢?

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

很简单,就是要把任务移除(因为可能添加成功了,只是线程启动失败了,所以要remove掉),还需要将线程池中的任务数-1(ctl变量的低29位,CAS的方式进行减一)。

上锁的目的很清楚了吧?workers.remove(w)是多线程并发执行的,所以需要上锁。

9、小结

  • 将任务添加到workers这个HashSet中
  • 开启一个Worker线程去执行任务
  • 执行的其实就是Worker对象的run方法
  • 执行成功则将任务从Workers这个HashSet中移除(Worker#run(),下篇来讲解)
  • 执行失败则将任务从workers这个HashSet中移除,且将线程池中线程数量-1(CAS的方式)

四、总结

  • 条件判断,比如线程池状态如果大于SHUTDOWN的话,则不接收新任务。

  • 判断没问题后,利用CAS的方式给线程数+1。

  • 将任务添加到workers这个HashSet中

  • 开启一个Worker线程去执行任务

  • 执行的其实就是Worker对象的run方法

  • 执行成功则将任务从Workers这个HashSet中移除(Worker#run(),下篇来讲解)

  • 执行失败则将任务从workers这个HashSet中移除,且将线程池中线程数量-1(CAS的方式)

【微信公众号】
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【原】编程界的小学生

没有打赏我依然会坚持。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值