述对ctl变量的cas操作失败了,则进行重试,再来一次循环
// else retry on failed CAS
}
}
如何保证工作线程一定能成功退出?
从上面tryTerminate方法的实现中可以看到,线程池必须等到所有工作线程都全部退出(workerCount为0),工作线程占用的全部资源都回收后才会推进到终止态。
那么之前启动的工作线程一定能通过processWorkerExit退出并销毁吗?答案是不一定,这主要取决于用户是否正确的编写了令工作线程安全退出的任务逻辑。
因为只有能退出任务执行逻辑(runWorker方法中的task.run())的工作线程才有机会执行processWorkerExit,无法从任务中跳出(正常退出or抛异常)的工作线程将永远无法退出,导致线程池也永远无法推进到终态。
下面分情况讨论:
- 任务中的逻辑是一定会执行完正常结束的(没有无限循环也没有令线程陷入阻塞态的操作)。那么这是没问题的
()->{
// 会正常结束的
System.out.println("hello world!");
};
- 任务中存在需要无限循环的逻辑。那么最好在循环条件内监听一个volatile的变量,当需要线程池停止时,修改这个变量,从而令任务从无限循环中正常退出。
()->{
// 无限循环
while(true){
System.out.println("hello world!");
}
};
()->{
// 无限循环时监听一个变量
while(!isStop) {
System.out.println("hello world!");
}
};
- 任务中存在Condition.await等会阻塞当前线程,令其无法自然退出的逻辑。
tryTerminate中停止工作线程时会调用Worker类的interruptIfStarted方法发出中断指令(Thread.interrupt方法),如果被阻塞的方法是响应中断的,那么业务代码中不能无脑吞掉InterruptedException,而要能感知到中断异常,在确实要关闭线程池时令任务退出(向上抛异常或正常退出)。
而如果是不响应中断的阻塞方法(如ReentrantLock.lock),则需要用户自己保证这些方法最终能够被唤醒,否则工作线程将无法正常退出而阻止线程池进入终止状态。
()->{
try {
new ReentrantLock().newCondition().await();
} catch (InterruptedException e) {
// doSomething处理一些逻辑后。。。
// 向上抛出异常
throw new XXXException(e);
}
}
()->{
try {
new ReentrantLock().newCondition().await();
} catch (InterruptedException e) {
}
// doSomething处理一些逻辑后。。。正常退出
}
为什么不在线程池终止时使用Thread.stop方法强制令工作线程停止呢?
虽然Thread.stop能够保证线程一定会被停止,但由于停止的过程中存在很严重的并发安全问题而被废弃而不推荐使用了。
具体原因可以参考官方文档(Why is Thread.stop deprecated?):https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
总结
- 本篇博客从源码的角度详细分析了jdk线程池ThreadPoolExecutor关于优雅停止实现的原理。其中重点介绍了ThreadPoolExecutor是如何做到中止后不能再受理新的任务、中止时不丢失已提交任务以及关闭时不会发生线程资源的泄露等核心功能。
- 结合之前发布的第一篇关于ThreadPoolExecutor正常运行时接受并执行所提交任务的博客,虽然没有100%的覆盖ThreadPoolExecutor的全部功能,但依然完整的讲解了ThreadPoolExecutor最核心的功能。希望这两篇博客能帮助到对jdk线程池实现原理感兴趣的读者。
- 本篇博客的完整代码在我的github上:https://github.com/1399852153/Reinventing-the-wheel-for-learning(ThreadPool模块 MyThreadPoolExecutorV2) 内容如有错误,还请多多指教。