JDK推荐的线程关闭方式
DK 在线程的 Stop 方法时明确不得强行销毁一个线程,要优雅的退出线程。
-
任务执行完成,或异常终止,任务认为无需再占用线程。 -
线程池根据当前任务执行情况,伸缩线程池。当任务执行较少时,退出空闲的线程。 -
服务或进程在关闭阶段,例如滚动发布时,需要退出线程、关闭线程池、关闭进程。 -
定时任务、周期任务需要终止执行时,需要退出当前线程。或者退出当前任务的执行。
while(config.isTaskEnable()) {
//从配置中心获取任务是否要终止
//循环执行业务逻辑。直到执行完成退出,或者被终止。
}
这种退出方式,是告知线程 “你应该在合适时机退出”, 由线程自己选择在合适的时机检查该状态。那么开发者在设计任务代码时,就要提前设计 合理的退出点,在退出点检查是否需要退出。
Thread.interrupt()
JDK 中提到了如果目标线程没有处于运行态,而是处于阻塞状态,自然无法检查退出的状态标记,如何通知这个线程退出呢?
JDK: 如果目标线程在一个条件变量上 wait,则其他线程应该使用 interrupt 方法中断目标线程。
interrupt 的 JDK 注释提到,
如果其他线程调用目标线程的 interrupt 方法,
- 恰好目标线程在调用。Object.wait(),object.join (),Object.sleep() 等方法时,目标线程的中断位标记被清除,同时目标线程会立即从 sleep、wait 等调用中恢复,并且被抛出 InterruptException。
- 如果目标线程在 IO 操作中被阻塞,例如 io.channels.InterruptibleChannel,Channel 将被关闭,线程的中断位被设置,同时目标线程收到 java.nio.channels.ClosedByInterruptException。
- 如果目标线程被阻塞在 java.nio.channels.Selector,线程中断状态被设置,然后目标线程立即从 select 中返回非零值。
- 如果其他条件都不成立,该线程中断位会被设置。
线程中断位标记了当前线程是否处于被中断状态,并且提供了 Thread.isInterrupted 方法查看当前是否处于中断位?那为什么目标线程阻塞在 Object.wait(),Sleep() 方法时,抛出了 interruptException,会取消标记呢?实际上 interrupt 操作执行两件事,1)设置中断位标记 2)通过 unpark 唤醒目标线程(park 和 unpark 分别可以阻塞线程和唤醒线程)。
然而目标线程醒来时会检查当前是否处于中断位,如果是 sleep 或者 wait 操作。如果处于中断位则取消中断位,抛出异常。取消中段位的原因应该是一种规范,即抛出中断异常,即通知了线程中断,无需再用中段位标记。
其他场景 2、场景 3 在被唤醒后,分别执行对应的中断响应策略。
interrupt 中断逻辑是确定的,业务线程要考虑自己是否调用了 sleep、wait 或者 io、selector 等操作,根据不同的场景,选择自己合适的中断响应策略。
那么推荐业务线程如何响应中断呢?
推荐的中断响应策略
立即响应中断
- 目标线程的任务在 InterruptedException 异常处理中,要主动回收资源,打印日志,退出任务执行。
- 目标线程如果没有阻塞操作,例如 sleep、wait。可以通过 Thread.isInterrupted(),查看当前中断位状态,如果被中断了,则采取以上第一步操作。
忽略中断,交给上一层处理
所谓上一层,可以理解为是调用堆栈的上一层,例如本层代码不负责处理中断这个场景,那么 Interrupt 异常被抛出后,可以选择如何方案:
- 抛出 InterruptedException 给上层,由上层代码处理。
- 调用 Thread.interrupt()。重新设置中断位标记 (自己中断自己)。由上游代码在本层方法返回后,检查中断位标记,进行中断处理。
当然最推荐的方式还是抛出 InterruptedException,让上游感知到下游调用链中存在阻塞,让上游对中断异常进行处理。
千万不要吞掉中断
什么是吞掉中断?例如当 sleep 抛出 InterruptedException 后,忽略异常,不执行任何操作,继续执行业务逻辑。
for (int i = 0; i < cnt; i++) {
try {
//执行业务逻辑
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("被中断");
}
System.out.println("子线程执行中");
}
while(true){
callChildMethod();//调用下游方法,但是下游吞掉了中断
if (Thread.currentThread().isInterrupted()) {
//回收资源,退出线程
}
}
-
不推荐强制销毁线程,会导致资源无法被释放,进行中请求无法正常处理完,导致业务数据处于不可知的状态。 -
Java 推荐优雅退出线程。 -
业务层可以使用字段标记,定期检查是否需要退出任务。 -
Thread.interrupt 中断目标线程、isInterrupted 查询中断位标记。 -
使用 Thread.interrupt 处理中断也可以优雅退出,但需要上下层堆栈都要关注中断,不得吞掉中断。
微信赞赏
支付宝扫码领红包