线程池中多余的线程是如何回收的?( 三 )


文章插图
 
阶段2 任务刚好要执行完了
这时任务已经快取完了,比如有4条工作线程,只剩下2个任务,那就可能出现2条线程获得任务,2条线程阻塞 。
因为在获取任务前的判断,没有加锁,那么会不会出现,所有线程都通过了前面的校验,来到workQueue获取任务的地方,刚好任务队列已经空了,线程全部阻塞了呢?因为shutdown() 已经执行完毕,无法再向线程发出中断信号,从而线程一直在阻塞,无法被回收 。
这种是不会发生的 。
假设有A,B,C,D四条工作线程,同时通过了条件1和条件2的判断,来到取任务的地方 。那么,工作队列至少还有一个任务,至少会有一条线程能取到任务 。
假设A,B获得了任务,C,D阻塞 。
A, B接下来的步骤是:
step1.任务执行完成后,再次getTask(),此时符合条件1,返回null,线程准备被回收 。
step2.processWorkerExit(Worker w, boolean completedAbruptly) 将线程回收 。
回收就只是把线程干掉这么简单吗?来看看processWorkerExit(Worker w, boolean completedAbruptly) 的方法 。

线程池中多余的线程是如何回收的?

文章插图
 
可以看到,在里面除了workers.remove(w) 移除线,还调用了tryTerminate() 。
线程池中多余的线程是如何回收的?

文章插图
 
第一个判断条件没有一个子条件符合,跳过 。第二个条件,工作线程还存在,那么随机中断一条空闲线程 。
那么问题就来了,中断一条空闲线程,也没说是一定中断正在阻塞的线程啊 。如果A, B同时退出,有没有可能出现A中断B, B中断A,AB互相中断,从而没有线程去中断唤醒阻塞的线程呢?
答案仍然是,想多了……
假设A能走到这里,说明A已经从工作线程的集合workers里面移除了(processWorkerExit(Worker w, boolean completedAbruptly) 在tryTerminate()之前,已经将其移除) 。那么A中断B,B来到这里中断,就不会在workers里面找到A了 。
线程池中多余的线程是如何回收的?

文章插图
 
也就是说,退出的线程不能互相中断,我从集合中退出后,中断了你,你不能中断我,因为我已经退出集合,你只能中断别人 。那么,即使有N个线程同时退出,至少在最后,也会有一条线程,会中断剩余的阻塞线程 。
就像多米诺骨牌一样,中断信号就会被传播下去 。
阻塞的C,D中的任意一条被中断唤醒后,又会重复step1的动作,周而复始,直到所有阻塞线程都被中断,唤醒 。
这也是为什么在tryTerminate()里面,传入false,只需要中断任意一条空闲线程的原因 。
想到这里,再次对Doug Lea心生钦敬(粤语)之情 。这设计得也太妙了叭 。
4. 总结ThreadPoolExecutor回收工作线程,一条线程getTask()返回null,就会被回收 。
分两种场景 。
1) 未调用shutdown() ,RUNNING状态下全部任务执行完成的场景线程数量大于corePoolSize,线程超时阻塞,超时唤醒后CAS减少工作线程数,如果CAS成功,返回null,线程回收 。否则进入下一次循环 。当工作者线程数量小于等于corePoolSize,就可以一直阻塞了 。
2) 调用shutdown() ,全部任务执行完成的场景shutdown() 会向所有线程发出中断信号,这时有两种可能 。
2.1)所有线程都在阻塞中断唤醒,进入循环,都符合第一个if判断条件,都返回null,所有线程回收 。
2.2)任务还没有完全执行完至少会有一条线程被回收 。在processWorkerExit(Worker w, boolean completedAbruptly)方法里会调用tryTerminate(),向任意空闲线程发出中断信号 。所有被阻塞的线程,最终都会被一个个唤醒,回收 。
如有不正确的地方,请大家指正




推荐阅读