在前面的线程池系列文章 线程池源码-线程全部关闭了吗 ,提到了线程池 shutdown 过程会使用线程的中断机制去关闭线程,试着问自己一个问题——调用线程的中断方法 interrupt() 就能中断线程吗?
起源
试想一下,你的电脑开着杀毒软件正杀着毒呢,你不耐烦地点击停止按钮企图停止这一过程,本质上来说就是一个线程想终止另外一个线程,这就要求线程间提供一种中断机制,传递中断信号。
而中断的策略也分两种:
- 暴力型
- 温柔协商型
暴力型
Thread.stop, Thread.suspend, Thread.resume 这些都是早期中断线程的方法,现在都已经废弃,原因在于它们都太暴力了,容易造成安全性问题。
下面我们举例说明:
public class Main {
public static void main(String[] args) throws InterruptedException {
InterruptThread thread = new InterruptThread();
thread.start();
Thread.sleep(1000);
// 暂停线程
thread.stop();
// thread.interrupt();
// 确保线程已经销毁
while (thread.isAlive()) { }
// 输出结果
thread.print();
}
}
class InterruptThread extends Thread {
private int x = 0;
private int y = 0;
@Override
public void run() {
// 这是一个同步原子操作
synchronized (this) {
++x;
try {
// 休眠3秒,模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++y;
}
}
public void print() {
System.out.println("x=" + x + " y=" + y);
}
}
输出结果:
可以看到我们模拟了一个原子操作,按照预期应该是要输出 x = 1 y = 1 的结果,但是线程在毫无察觉的情况下被中断了,有点像被别人"敲了闷棍",根本没有反应的机会。
温柔协商型
把上面测试代码中的中断策略换成 thread.interrupt();
输出结果:
可以看到,中断操作抛了异常,并且我们能够 catch 住这个异常,进行后续的处理,使得最终输出结果符合预期。
interrupt() 设计理念是『一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止』,是一种比较温柔的做法,它更类似一个标志位。其作用不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- 在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)
下面来看一下 Thread 类 interrupt 相关的几个方法。
// 核心 interrupt 方法
public void interrupt() {
// 非本线程,需要检查权限
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
// 设置中断标志位
interrupt0();
// 调用如 I/O 操作定义的中断方法
b.interrupt(this);
return;
}
}
interrupt0();
}
// 静态方法,需要注意调用该方法会清除中断状态。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 这个方法不会清除中断状态,仅仅返回中断状态
public boolean isInterrupted() {
return isInterrupted(false);
}
// 上面两个方法会调用这个本地方法,参数代表是否清除中断状态
private native boolean isInterrupted(boolean ClearInterrupted);
首先介绍一下 interrupt() 方法:
-
非自身线程进行中断操作时,需要进行权限检测
-
线程处于 sleep, wait, join 状态,是可以响应中断的,会立即抛出 InterruptedException 异常
-
如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常
-
如果线程在Selector上被阻塞,select方法将立即返回;
-
如果非以上情况,将直接标记 interrupt 状态;
阻塞情况下中断,抛出异常后线程恢复非中断状态,即 interrupted = false
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptTask("1"));
thread.start();
thread.interrupt();
}
}
class InterruptTask implements Runnable {
String name;
public InterruptTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread has been interrupt!");
}
System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
System.out.println("task " + name + " is over");
}
}
输出结果:
调用Thread.interrupted() 方法后线程恢复非中断状态
class InterruptTask implements Runnable {
String name;
public InterruptTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("first :" + Thread.interrupted());
System.out.println("second:" + Thread.interrupted());
System.out.println("task " + name + " is over");
}
}
输出结果:
总结
通过本篇文章,我们了解了:
- 传统的中断策略——暴力型,例如 thread.stop 方法
- 安全的中断策略,线程提供的 interrupted 操作以及它的中断机制
- 线程如果有中断的需求,需要自身去响应中断
如果觉得文章对你有帮助,欢迎留言点赞。