线程中断
通过 stop 来终止线程太过粗鲁, 且不安全, 可能有某些资源没有关闭, 锁没有释放等造成问题
通过 interrupt 给线程设置中断标记, 是否响应中断, 由线程自己来决定, 可以更温和地在线程安全点终止线程
可以通过线程中断标记, 在一定程度上做到线程通信
中断说明
thread.interrupt()
, 给指定线程设置中断标记
thread.isInterrupted()
, 获取指定线程的中断标记
Thread.interrupted()
, 获取指定线程的中断标记, 且有标记则清除
如果线程执行 Object.wait()
/ Thread.sleep()
/ Thread.join()
前被设置了中断标记, 则执行到这些代码时, 不会睡眠或阻塞, 直接抛出中断异常, 并清除中断标记
如果线程正在执行 Object.wait()
/ Thread.sleep()
/ Thread.join()
中时被设置了中断标记, 则直接唤醒线程, 抛出中断异常, 并清除中断标记
代码验证
@Test
@SneakyThrows
public void test() {
// thread.isInterrupted(), 获取中断标记
// thread.interrupt(), 设置中断标记, 但不会改变线程的运行状态
// Thread.interrupted(), 获取中断标记, 有则清除
// Thread.sleep(), 分两种情况, 1.线程sleep前被设置中断,执行到sleep时,不睡,直接抛出中断异常,并清除中断标记, 2.线程sleep中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记
// object.wait(), 分两种情况, 1.线程wait前被设置中断,执行到wait时,不阻塞,直接抛出中断异常,并清除中断标记, 2.线程wait中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记
// Thread.join(), 分两种情况, 1.线程join前被设置中断,执行到join时,不阻塞,直接抛出中断异常,并清除中断标记(本质上是wait), 2.线程join中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记(本质上是wait)
Thread thread = new Thread(() -> {
Thread me = Thread.currentThread();
log.info("默认是没有中断标记的: {}", me.isInterrupted());
System.out.println();
// 给线程设置中断标记
me.interrupt();
log.info("设置中断标记后: {}", me.isInterrupted());
System.out.println();
// 设置中断标记
me.interrupt();
// 获取线程中断标记, 会清除中断标记
// 内部是 Thread.currentThread().isInterrupted(true);
log.info("调用 Thread.interrupted() 返回的值: {}", Thread.interrupted());
log.info("调用 Thread.interrupted() 会清除中断标记: {}", me.isInterrupted());
// 清除中断标记
Thread.interrupted();
System.out.println();
// 设置中断标记
me.interrupt();
// 主动抛出中断异常, 是不会清除线程的中断标记的
try {
throw new InterruptedException();
} catch (InterruptedException e) {
log.info("主动抛出中断异常, 不会清除中断标记: {}", me.isInterrupted());
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 设置中断标记
me.interrupt();
// 线程sleep前被设置中断,执行到sleep时,不睡,直接抛出中断异常,并清除中断标记
try {
log.info("子线程开始 sleep:10 秒");
// 本质上调用的是 Thread.sleep()
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
log.info("线程sleep前被设置中断,执行到sleep时,不睡,直接抛出中断异常,并清除中断标记: {}", me.isInterrupted());
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 线程sleep中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记
try {
log.info("子线程开始 sleep:10 秒");
// 本质上调用的是 Thread.sleep()
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
log.info("线程sleep中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记: {}", me.isInterrupted());
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 设置中断标记
me.interrupt();
// 线程wait前被设置中断,执行到wait时,不阻塞,直接抛出中断异常,并清除中断标记
synchronized (this) {
try {
log.info("子线程开始 wait:10 秒");
wait(1000 *10);
} catch (InterruptedException e) {
log.info("线程wait前被设置中断,执行到wait时,不阻塞,直接抛出中断异常,并清除中断标记: {}", me.isInterrupted());
}
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 线程wait前被设置中断,执行到wait时,不阻塞,直接抛出中断异常,并清除中断标记
synchronized (this) {
try {
log.info("子线程开始 wait:10 秒");
wait(1000 *10);
} catch (InterruptedException e) {
log.info("线程wait中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记: {}", me.isInterrupted());
}
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 子线程里再开一个需要执行10秒的新子线程
Thread other = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动该新子线程
other.start();
// 设置中断标记
me.interrupt();
// 线程join前被设置中断,执行到join时,不阻塞,直接抛出中断异常,并清除中断标记(本质上是wait)
try {
// 让子线程me等待子线程other之行结束
log.info("子线程开始 join:10 秒");
other.join(1000 * 10);
} catch (InterruptedException e) {
log.info("线程join前被设置中断,执行到join时,不阻塞,直接抛出中断异常,并清除中断标记(本质上是wait): {}", me.isInterrupted());
}
// 清除中断标记
Thread.interrupted();
System.out.println();
// 线程join中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记(本质上是wait)
try {
// 让子线程me等待子线程other之行结束
log.info("子线程开始 join:10 秒");
other.join(1000 * 10);
} catch (InterruptedException e) {
log.info("线程join中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记(本质上是wait): {}", me.isInterrupted());
}
// 清除中断标记
Thread.interrupted();
System.out.println();
log.info("子线程结束");
System.out.println();
});
thread.start();
TimeUnit.SECONDS.sleep(1);
log.info("主线程睡1秒后给子线程设置中断标记, 测试sleep中被设置中断");
thread.interrupt();
TimeUnit.SECONDS.sleep(1);
log.info("主线程再睡1秒后给子线程设置中断标记, 测试wait中被设置中断");
thread.interrupt();
TimeUnit.SECONDS.sleep(1);
log.info("主线程再睡1秒后给子线程设置中断标记, 测试join中被设置中断");
thread.interrupt();
TimeUnit.SECONDS.sleep(1);
log.info("主线程再睡1秒等待子线程测试完成");
log.info("主线程结束");
}
[20220518.173753.647][INFO ][Thread-1] 默认是没有中断标记的: false
[20220518.173753.650][INFO ][Thread-1] 设置中断标记后: true
[20220518.173753.650][INFO ][Thread-1] 调用 Thread.interrupted() 返回的值: true
[20220518.173753.650][INFO ][Thread-1] 调用 Thread.interrupted() 会清除中断标记: false
[20220518.173753.650][INFO ][Thread-1] 主动抛出中断异常, 不会清除中断标记: true
[20220518.173753.651][INFO ][Thread-1] 子线程开始 sleep:10 秒
[20220518.173753.651][INFO ][Thread-1] 线程sleep前被设置中断,执行到sleep时,不睡,直接抛出中断异常,并清除中断标记: false
[20220518.173753.651][INFO ][Thread-1] 子线程开始 sleep:10 秒
[20220518.173754.652][INFO ][main] 主线程睡1秒后给子线程设置中断标记, 测试sleep中被设置中断
[20220518.173754.652][INFO ][Thread-1] 线程sleep中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记: false
[20220518.173754.652][INFO ][Thread-1] 子线程开始 wait:10 秒
[20220518.173754.652][INFO ][Thread-1] 线程wait前被设置中断,执行到wait时,不阻塞,直接抛出中断异常,并清除中断标记: false
[20220518.173754.652][INFO ][Thread-1] 子线程开始 wait:10 秒
[20220518.173755.656][INFO ][main] 主线程再睡1秒后给子线程设置中断标记, 测试wait中被设置中断
[20220518.173755.656][INFO ][Thread-1] 线程wait中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记: false
[20220518.173755.656][INFO ][Thread-1] 子线程开始 join:10 秒
[20220518.173755.656][INFO ][Thread-1] 线程join前被设置中断,执行到join时,不阻塞,直接抛出中断异常,并清除中断标记(本质上是wait): false
[20220518.173755.656][INFO ][Thread-1] 子线程开始 join:10 秒
[20220518.173756.668][INFO ][main] 主线程再睡1秒后给子线程设置中断标记, 测试join中被设置中断
[20220518.173756.668][INFO ][Thread-1] 线程join中被设置中断,直接唤醒线程,抛出中断异常,并清除中断标记(本质上是wait): false
[20220518.173756.668][INFO ][Thread-1] 子线程结束
[20220518.173757.682][INFO ][main] 主线程再睡1秒等待子线程测试完成
[20220518.173757.682][INFO ][main] 主线程结束
使用
@Test
@SneakyThrows
public void demo() {
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
try {
log.info("开始做某事");
Thread.sleep(600);
log.info("结束做某事");
} catch (InterruptedException e) {
e.printStackTrace();
log.info("中断异常, 清理资源");
// 如果在sleep/wait/join时被设置中断, 中断标记会被清除
// 针对该循环中的中断异常, 需要自己补充一下中断标记, 不然循环不会退出, 这里可以注释下来看运行效果
Thread.currentThread().interrupt();
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
// 主线程终止的话, 子线程也会跟着终止, 所以让主线程多运行下, 方便看到 中断异常没有导致线程终止 的效果
TimeUnit.SECONDS.sleep(1);
}
[20220518.181000.744][INFO ][Thread-1] 开始做某事
[20220518.181001.357][INFO ][Thread-1] 结束做某事
[20220518.181001.357][INFO ][Thread-1] 开始做某事
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at jdk.java.lang.ThreadInterruptTest.lambda$demo$2(ThreadInterruptTest.java:187)
at java.lang.Thread.run(Thread.java:748)
[20220518.181001.749][INFO ][Thread-1] 中断异常, 清理资源