原来,这才是 JDK 推荐的线程关闭方式
共 8005字,需浏览 17分钟
·
2023-11-06 18:35
点击关注公众号,Java干货及时送达
JDK 在线程的 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 处理中断也可以优雅退出,但需要上下层堆栈都要关注中断,不得吞掉中断。
转自:五阳神功,
链接:juejin.cn/post/7291564831710445622