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

author:编程界的小学生

date:2021/05/30

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

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

  • 为什么Worker继承AQS?
  • runWoker都干了哪些事?
  • 我想在run方法真正执行自己业务代码的前后各自打印log怎么做?
  • 非核心线程是怎么结束的?核心线程又是怎么长期存活不会销毁的?

一、runWorker全貌

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        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);
                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);
    }
}

二、拆解runWorker

1、大有学问的unlock

// 获取当前线程
Thread wt = Thread.currentThread();
// 获取当前任务
Runnable task = w.firstTask;
w.firstTask = null;
// 解锁???这好端端的也没加锁,怎么突然来个unklock?
w.unlock();

Worker哪来的?我们在addWorker的时候new的,来看下Worker构造器:

// 哦豁,继承了AQS
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    Worker(Runnable firstTask) {
        // 设置AQS是state为-1,主要目的是为了不让中断。
        setState(-1);
        // 设置任务
        this.firstTask = firstTask;
        // 创建线程
        this.thread = getThreadFactory().newThread(this);
    }
}

好了,我们知道构造器里面干了三件事,但是我们这里只关注第一件事,那就是setState(-1);,state是AQS的变量,-1啥意思?没啥意思,很简单,就是说我这个任务不可以被中断。那为啥要这么设置?废话,你都没开始执行呢,你只是new一个任务出来,线程都没启动,怎么可能允许中断呢?恍然大悟…!

那再看unlock干了啥?

public void unlock() {
    release(1);
}

狗日的…!调用AQS的release方法,给state释放1,也就是说unlock后state变成了0,通俗点就是:我现在任务得到了执行,我要让他允许中断了,怎么允许?当然是state=0,这都是AQS的知识!

所以为啥Worker要继承AQS?因为他巧妙的运用了AQS的中断。

2、while循环拿任务

while (task != null || (task = getTask()) != null) {
    ...
}

task一上来就是w.firstTask;,也就是说我们在addWorker方法里包装在Worker里的任务,第一次肯定不是null,所以会执行while循环体,执行完后再finally里给task弄成了null。所以这个条件仅在第一次执行的时候为true,因为每次finally都会把task弄成null。

task = getTask():从队列里取任务,取出来后赋值给task,如果队列里有任务就执行循环体,执行完成后会给任务从队列里remove掉,如果getTask获取不到任务则会阻塞,因为底层是BlockingQueue<Runnable>getTask方法后面分析。

3、中断判断

/*
 * 线程池状态是大于等于STOP的话(执行了shutdownNow),
 * 并且你没有被中断过的话(!wt.isInterrupted()),则让线程中断,
 * 也就是说,线程池状态都是大于等于STOP的了,那么设置中断标记位,告诉这个线程说:
 * 你小子别找事啊,你赶紧给我回来,你被开除(中断)了。
 */
if ((runStateAtLeast(ctl.get(), STOP) ||
     (Thread.interrupted() &&
      runStateAtLeast(ctl.get(), STOP))) &&
    !wt.isInterrupted())
    wt.interrupt();

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

4、beforeExecute

beforeExecute(wt, task);

在线程任务开始执行前做一些处理,可以自定义实现方法。模板方法。需要注意的地方是他只被try finally包起来了,没有catch,也就是说异常会被吞,即使报错,如果用户不做catch捕获的话,那么将不会影响线程下面的工作。这很关键,对下面的completedAbruptly有决定性的作用。

5、任务执行

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

开始执行任务,也就是你提交到线程池里的任务,且捕获异常

6、afterExecute

afterExecute(task, thrown);

在线程任务执行之后做一些处理,可以自定义实现方法。模板方法。需要注意的地方是他只被try finally包起来了,没有catch,也就是说异常会被吞,即使报错,如果用户不做catch捕获的话,那么将不会影响线程下面的工作。这很关键,对下面的completedAbruptly有决定性的作用。

7、finally

好奇怪啊,为啥afterExecute那里的解释我说他被try finally包起来了?哪被包起来了?我说错了?看下面代码:

try {
    beforeExecute(wt, task);
    try {
        task.run();
    } catch (RuntimeException x) {
        ...
    } finally {
        afterExecute(task, thrown);
    }
} finally {
    task = null;
    w.completedTasks++;
    w.unlock();
}

好家伙,afterExecute确实被包起来了,被这段代码包起来了:

try {
    afterExecute(task, thrown);
} finally {
    task = null;
    w.completedTasks++;
    w.unlock();
}

首先可以发现也没有被catch捕获。其次就是一些辅助工作,比如task弄成null来辅助最外层的while循环,完成的任务数+1,解锁的工作。

剩余没讲的代码还有最后一段,那就是:processWorkerExit,再说这个方法之前,必须看下前面提到两次的completedAbruptly是个什么鬼。

8、completedAbruptly

// 默认是true
boolean completedAbruptly = true;
try {
    while (task != null || (task = getTask()) != null) {
     	...
        try {
            beforeExecute(wt, task);
            try {
                task.run();
            } catch (Throwable x) {
                ...
            } finally {
                afterExecute(task, thrown);
            }
        } finally {
           ...
        }
    }
    // 这里设置成false
    completedAbruptly = false;
} finally {
    processWorkerExit(w, completedAbruptly);
}

很简单,如果任务正常得到执行,没有任何异常,他就是false,如果中途发生了异常,那就是true。具体含义是是否发生了中断,而且这个中断还是用户自己中断的,因为比如beforeExecute、afterExecute啥的都是重新才可能发生异常。

9、processWorkerExit

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果任务执行过程中发生了报错,则CAS的方式把任务数-1。ctl的低29位。
    if (completedAbruptly)
        // CAS的方式将任务数-1。
        decrementWorkerCount();

    // 上锁 保证将任务移除队列的线程安全。
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 将任务移除队列,因为任务已经执行完了嘛
        workers.remove(w);
    } finally {
        // 解锁
        mainLock.unlock();
    }
	// 钩子函数,下一篇幅讲解
    tryTerminate();

    /*
     * 下面这段代码的含义是如果线程池是RUNNING或者SHUTDOWN状态的话,
     * 且任务顺利完成(completedAbruptly=false)的话,那么判断是否设置了允许核心线程超时
     * 如果允许核心线程超时,且任务队列不等于空的话,那么开启一个线程来执行任务。
     * 
     * 一言以蔽之:如果线程池是RUNNING或者SHUTDOWN状态的话,且任务队列不是空,那么至少保证线程池中有一个线程在执行任务
     */
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return;
        }
        addWorker(null, false);
    }
}

10、小结

  • 先unlock调用AQS的release方法,让任务可中断。(因为任务已经开始执行了,可以中断了)
  • while循环拿任务,没任务就阻塞,采取的BlockingQueue的阻塞api
  • 中断判断,线程池状态是大于等于STOP的话(执行了shutdownNow),就让线程中断
  • 线程执行前会先执行beforeExecute,可重写
  • 真正的任务执行
  • 线程执行前会先执行afterExecute,可重写
  • 执行完成后将任务从workers里remove掉
  • 如果线程池是RUNNING或者SHUTDOWN状态的话,且任务队列不是空,那么至少保证线程池中有一个线程在执行任务

三、getTask全貌

private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

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

四、拆解getTask

1、timed

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  • 如果设置了核心线程允许超时的话,则timed=true,反之false。

  • 如果线程池中活跃线程数大于核心线程数,则timed=true,反之false。

timed干啥用的?不是傻子的话都该看出来了,释放线程用的,也就是说非核心线程(大于核心线程数了)要被释放,允许核心线程超时的话也要被释放。

2、释放线程

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

如果timed是true,那么走workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)也就是设置获取任务的超时时间,到时间后还没获取到任务的话则会timeOut=true。

try {
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

timeOut=true的话,getTask有个判断会让其跳出循环,线程生命周期也自然而然的随之结束。

if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;
    continue;
}

反之如果timed是false的话,那么会执行workQueue.take();不带超时时间的,则一直阻塞等待有结果返回。

3、小结

其实一句话就概括了:从队列中获取任务,如果timed是true的话,则调用阻塞队列的poll方法阻塞一段时间获取任务,这段时间没任务的话,则超时设置timeOut=true,结束生命周期。否则调用take()方法一直阻塞等待任务到来,也就是核心线程为什么能一直存活的原因。

五、总结

  • runWorker方法就是执行任务的,执行前后可以实现方法自定义干一些事情。
  • 任务执行完了后就会从workers这个HashSet里移除
  • 核心线程一直存活的方法是调用阻塞队列的take(),不带超时时间,一直阻塞等待任务到来。
  • 非核心线程超时后就结束的方法是调用阻塞队列的poll(long time),让其这段时间内没任务的话就结束方法生命周期,自然而然的线程跟着销毁。

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

【原】编程界的小学生

没有打赏我依然会坚持。

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

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

打赏作者

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

抵扣说明:

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

余额充值