运行工作线程
肖文英
本章节我们继续分析 runWorker 源码,由于这个方法源码比较复杂,所以我们可以先问问自己以下问题,然后带着问题去阅读源码:
为什么 Worker 继承 AQS?
runWorker 都干了哪些事?
想在 run 方法真正执行自己业务代码的前后打印 log 怎么做?
非核心线程是怎么结束的?核心线程又是怎么长期存活不会销毁的?
1 runWorker 全貌
2 拆解
1 大有学问的 unlock
好了,我们知道构造器里面做了三件事,但是我们这里只关注第一件事,那就是 setState(-1),state 是 AQS 的变量,-1 表示这个任务不可以被中断。因为没开始执行,我们只是 new 一个 Worker 出来,线程没启动,所以不允许中断。
那再看 unlock 做了什么?
调用 AQS 的 release 方法,把 state 设置为 0,这意味着:现在任务得到了执行,我们允许中断这个 worker 了。
所以 Worker 继承 AQS 是为了使用 AQS 的中断机制。
2 while 循环拿任务
task 是 w.firstTask,也就是说我们在 addWorker 方法里包装在 Worker 里的任务,第一次肯定不是 null,所以会执行 while 循环体,执行完后在 finally 里把 task 设置 null。所以task != null这个条件仅在第一次执行的时候为 true。
task = getTask():从队列里取任务,取出来后赋值给 task,如果队列里有任务从队列里 remove 然后执行循环体,如果 getTask 获取不到任务则会阻塞,因为底层是 BlockingQueue,getTask 方法会在后面章节分析。
3 中断判断
情况 1 语句 1-1 和语句 2 组合:线程池状态>=STOP && 中断标志位为 false
情况 2 语句 1-2 和语句 2 组合:当前工作线程已被中断 && 线程池状态>=STOP && 中断标志位为 false。Thread.interrupted() 会检查并清除当前线程的中断状态,所以如果这里返回 true,意味着在调用这个方法之前,当前线程确实被中断了。
如果情况 1 或情况 2 满足(线程池正在停止或已停止或者当前线程被中断且线程池也在停止或已停止状态,且目标工作线程中断标识位为 false),则调用 wt.interrupt() 来中断工作线程。
注意我们在前面执行了 w.lock(),那么这种 check-then-act 操作是线程安全的。
为什么需要这样的逻辑?
线程池停止:当线程池处于 STOP 状态时,我们希望确保所有工作线程都被中断,以便它们能够尽快停止执行并退出。
处理外部中断:在某些情况下,可能希望响应外部中断(例如,用户按下了 Ctrl+C),并尝试中断线程池中的所有线程。然而,由于线程池可能正在处理其他关闭逻辑(如优雅地关闭),因此仅当线程池也处于停止状态时,才应响应这种外部中断。
避免重复中断:中断状态是线程的一个属性,一旦设置,就会保持到该线程检查它为止(除非它被清除)。因此,在尝试中断线程之前,检查它是否已经被中断是一个好习惯。
4 beforeExecute
在线程任务开始执行前做一些处理,可以自定义实现方法。需要注意的地方是它只被 try-finally 包起来了,没有 catch,也就是说即使 beforeExecute 抛出异常,也不会影响线程下面的工作 (finally 语句块)。
注意由于我们没有捕获异常,那么这个异常导致我们从 while 循环中退出,进而导致线程运行结束。
5 任务执行
这个方法使用了一种异常设计思想:我们通过多个 catch 语句块捕获各种异常,是为了记录抛出的异常,然后我们在后面逻辑处理时可以使用这个异常。
第三个 catch 块捕获 Throwable 的所有实例,这包括了 Exception 和 Error。由于前两个 catch 块已经分别捕获了 RuntimeException 和 Error,这个 catch 块实际上只会捕获那些既不是 RuntimeException 也不是 Error 的 Throwable 实例。这个 catch 块的存在可能是为了处理一些极端情况或未来的扩展。在这个 catch 块中,捕获到的 Throwable 被封装在一个新的 Error 对象中并重新抛出。
6 afterExecute
在线程任务执行之后做一些处理,可以自定义实现方法。由于 afterExecute 是在 finally 语句块中执行的,所以不论 try 语句块如何执行,它都会被执行。
同理如果我们在 afterExecute 抛出了异常,那么这个异常导致我们从 while 循环中退出,进而导致线程运行结束。
7 finally
两层 try finally 结构:
afterExecute 确实被包起来了,可以理解为被这段代码包起来了:
首先可以发现也没有 catch 捕获。其次就是一些辅助工作,比如 task 设置 null 来辅助最外层的 while 循环,完成的任务数 +1,解锁的工作。
剩余没讲的代码还有最后一段,那就是:processWorkerExit,在说这个方法之前,必须看下前面提到两次的 completedAbruptly 的作用。
8 completedAbruptly
如果任务正常得到执行,没有任何异常,它就是 false,如果中途发生了异常,那就是 true,因为在前面的执行逻辑中抛出了异常,所以不会执行 completedAbruptly = false。
所以 completedAbruptly 变量被用来区分任务是正常完成还是由于发生异常被迫停止的,从而允许我们采取不同的后续行动。
9 processWorkerExit
分析完 processWorkerExit 方法我们可以知道如果我们创建了最大线程数为 1 的线程池,并且需要被执行的任务可能会抛出异常,那么会导致工作线程退出,但是最终在这个方法中线程池做了兜底处理:如果线程池没有退出或正在退出但是任务队列中存在待执行的任务,那么线程池会创建新的工作线程来保证线程池是可用的,这和我们使用 Thread 处理任务是有区别的。
3 小结
先 unlock 调用 AQS 的 release 方法,让 worker 可以响应中断(因为工作线程已经开始执行了)
while 循环拿任务,没任务就阻塞,getTask 内部采取的 BlockingQueue 的阻塞方法
中断判断,线程池状态是大于等于 STOP 的话(执行了 shutdownNow),就让线程中断
线程执行前会先执行 beforeExecute,可重写
真正的任务执行
线程执行后会执行 afterExecute,可重写
从 while 循环跳出后将工作线程从 workers 里 remove 掉
如果线程池是 RUNNING 或者 SHUTDOWN 状态的话,且任务队列不是空,那么至少保证线程池中有一个线程可以执行任务
公开
同步至部落
取消
完成
0/2000
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 线程池源码解读与实践》
《Java 线程池源码解读与实践》
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
精选留言
由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。
收起评论