Java中断
Java中断
引言:为什么需要线程中断?
在并发编程中,我们经常需要协调不同线程的执行。
有时,一个线程需要通知另一个线程停止其当前正在执行的任务。
你可能会想到一些看似直接的方法,比如调用 Thread.stop() 或 Thread.suspend()。然而,这些方法已被明确废弃 (Deprecated),因为它们存在严重的缺陷:
- Thread.stop(): 强制终止线程,不给线程任何清理资源(如释放锁、关闭文件、网络连接等)的机会。这可能导致对象状态不一致,引发难以预料的错误。想象一下,一个线程正在修改共享数据,只修改了一半就被 stop() 了,这会留下一个“残缺”的数据结构,其他线程使用时就会出错。
- Thread.suspend() 和 Thread.resume(): 容易导致死锁。如果一个线程在持有锁的情况下被 suspend(),它将永远不会释放该锁,其他需要该锁的线程将无限期等待。
为了解决这些问题,Java 引入了线程中断 (Thread Interruption) 机制。线程中断并非强制终止线程,而是一种协作式的通信机制。它允许一个线程向另一个线程发送一个“请求停止”的信号,而被请求的线程可以自行决定如何以及何时响应这个信号,从而有机会进行必要的清理工作,实现“优雅地”停止。
个人总结:java中断的作用就是去通知一个线程停止,这个线程可以收到停止通知后(抛InterruptedException异常),进行一些代码处理(捕获InterruptedException异常),这比直接停止线程(Thread.stop() )或 Thread.suspend()好。
// 这种模式让"停止"变得可控,给了线程一个执行清理工作的机会。
try {
// 主要工作
doRealWork();
} catch (InterruptedException e) {
// 停止时的清理工作
doCleanup();
}
中断
中断流程确实可以概括为:
- A线程调用
B.interrupt() - B线程的中断状态变为
true - B线程检测到中断状态后做出相应处理
简单总结来说就是A线程让另一个B线程中断,B变成中断状态,然后B线程中断状态下做处理,中断就是为了让B线程去做这个处理。
// A线程
B.interrupt(); // 做两件事:
// 1. 设置B的中断标志为true
// 2. 如果B在sleep/wait/join中,会抛出InterruptedException
中断是一种协作机制:线程A通过设置标志和唤醒的方式通知线程B"有事需要关注",线程B检测到后根据自己的业务逻辑决定如何响应这个通知。
中断 = 设置标志 + 唤醒阻塞 + 让目标线程自行决定如何响应
// 发起方A:发送通知
threadB.interrupt(); // "B,有事找你!"
// 接收方B:自行决定如何响应
public void run() {
// 方式1:终止
if (Thread.currentThread().isInterrupted()) {
cleanup();
return;
}
// 方式2:处理事件后继续
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
handleEvent(); // 处理事件
// 继续运行
}
// 方式3:忽略(不推荐)
// Thread.interrupted(); // 清除标志,假装没发生过
}
响应中断和忽略中断的任务
@Slf4j
public class CancelDemo {
public static void main(String[] args) throws Exception {
// 示例1:响应中断的任务
FutureTask<String> task1 = new FutureTask<>(() -> {
log.error("任务1计算开始");
try {
long sum = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
// 定期检查中断(每10000次检查一次)
if (i % 10000 == 0 && Thread.currentThread().isInterrupted()) {
log.error("任务1被中断,当前 sum = " + sum);
// 可以选择:
// 1. 抛出 InterruptedException(如果声明了)
// 2. 返回部分结果
// 3. 抛出 RuntimeException
// 如果你想遵循"抛出 InterruptedException 后清除中断标志"的惯例:可以显式清除中断标志
// Thread.interrupted(); // 清除标志
throw new InterruptedException("任务1被中断");
}
}
log.error("任务1计算结果"+ sum);
return "sum: " + sum;
} catch (InterruptedException e) {
log.error("任务1被中断, 中断标识" + Thread.currentThread().isInterrupted());
throw e; // 重新抛出
}
});
// 示例2:忽略中断的任务
FutureTask<String> task2 = new FutureTask<>(() -> {
log.error("任务2计算开始");
long sum = 0;
for (long i = 0; i < Long.MAX_VALUE; i++) {
sum += i;
// 不检查中断!
// 每10亿次打印一次进度
if (i % 500_000_000 == 0 && i > 0) {
log.error("进度: i=" + i + ", sum=" + sum + ", 中断标识" + Thread.currentThread().isInterrupted());
}
// 为了演示,我们设个上限,不然真的运行不完
if (i > 10_000_000_000L) {
log.error("达到上限,提前结束");
break;
}
}
log.error("任务2计算结果"+ sum);
return "sum: " + sum;
});
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(task1);
executor.submit(task2);
Thread.sleep(1000);
log.error("取消任务1: " + task1.cancel(true)); // true
log.error("取消任务2: " + task2.cancel(true)); // true
// 任务1:可以看到任务1确实被中断了
// 任务2:会执行完所有逻辑,但是这时候状态已经是INTERRUPTED了,最后的结果不会set成功,因为set方法会判断状态NEW的才能设置成COMPLETING,才能赋值outcome。
// protected void set(V v) {
// if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// outcome = v;
// UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// finishCompletion();
// }
// }
// new Thread().interrupt();
// 中断只是去设置个标志,目标线程自行决定如何响应。
// 如果目标线程因为调用阻塞方法(wait, sleep, join)而处于阻塞状态,
// 那么调用 interrupt() 不仅会设置中断标志位,还会立即唤醒该线程,并使其抛出 InterruptedException 异常。
// 特别注意:在抛出 InterruptedException 的同时,JVM 会清除该线程的中断标志位,即将其重新设置为 false! 这是 InterruptedException 的一个非常关键的特性。
}
}
浙公网安备 33010602011771号