外观
正确关闭线程
约 1574 字大约 5 分钟
2025-11-01
线程关闭是一项技术活,稍有使用不当,非常容易导致bug。
识别 interrupt、isInterrupted、interrupted 方法
interrupt
一般的线程,在被interrupt方法中断后,线程执行会中断?
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
}
// 中断标记置为true
interrupted = true;
interrupt0(); // 中断vm线程
// thread may be blocked in an I/O operation
if (this != Thread.currentThread()) {
Interruptible blocker;
synchronized (interruptLock) {
blocker = nioBlocker;
if (blocker != null) {
blocker.interrupt(this);
}
}
if (blocker != null) {
blocker.postInterrupt();
}
}
}从上面可以看出,调用interrupt方法,先把中断标记置为true,然后再调用native方法,通知虚拟机中断线程。这个时候如果去判断当前线程是否被中断,是没问题的。⚠️这里被中断的线程并不会停止运行,看看下面这段示例代码:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
}
});
t1.start();
// 先让 t1 线程运行
TimeUnit.SECONDS.sleep(1);
// isInterrupted() 可以获取到线程的打断标记,如果线程被打断,则打断标记为 true,并且该方法不会清除打断标记
System.out.println("before interrupt status >>> {"+t1.isInterrupted()+"}");
// 打断正在运行的线程/park 状态的线程会重新设置打断标记,打断 sleep、join、wait 状态的线程会抛出 InterruptedException 异常,并且会清除打断标记
t1.interrupt();
System.out.println("after interrupt status >>> {"+t1.isInterrupted()+"}" );
}执行结果
before interrupt status >>> {false}
after interrupt status >>> {true}这里打印的标识已经被置为true,但是被执行interrupt()方法的线程,还是在运行中,说明interrupt()只是设置了一个标识。
如果被中断的线程,在sleep/wait/join,那么还能准确拿到被中断线程的状态吗?
看看下面这段代码和执行结果
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
// t1 线程休眠 1000s
TimeUnit.SECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("{"+Thread.currentThread().getName()+"} 线程被打断了");
}
}, "t1");
t1.start();
// 主线程休眠 2s,让 t1 线程有足够的时间进入 sleep 状态
TimeUnit.SECONDS.sleep(2);
System.out.println("before interrupt status >>> {"+ t1.isInterrupted()+"}");
t1.interrupt();
// 主线程休眠 2s,让 t1 线程有足够的时间进行 interrupt 操作
TimeUnit.SECONDS.sleep(2);
System.out.println("after interrupt status >>> {"+t1.isInterrupted()+"}");
}执行结果
before interrupt status >>> {false}
{t1} 线程被打断了
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleepNanos0(Native Method)
at java.base/java.lang.Thread.sleepNanos(Thread.java:496)
at java.base/java.lang.Thread.sleep(Thread.java:564)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at Main.lambda$main$0(Main.java:13)
at java.base/java.lang.Thread.run(Thread.java:1575)
after interrupt status >>> {false}即使线程被中断,拿到的中断标识还是false
打断 sleep、wait、join 的线程会抛出 InterruptedException 异常并清除打断标记,如果打断正在运行的线程、park 的线程则会重新设置打断标记
| 断时线程的状态 | 中断后线程的中断标记 | 中断后线程运行情况 |
|---|---|---|
| 正常运行的线程 | false -> true | 线程正常运行 |
| sleep、wait、join 状态的线程 | false -> true -> false | 线程抛出异常,中止运行 |
isInterrupted 和 interrupted 的区别
先看一下isInterrupted()
public boolean isInterrupted() {
// ClearInterrupted 参数的含义是否清除打断标记
// false 代表不清除,打断之后 false -> true
// true 代表清除,打断之后会重置打断标记 false -> true -> false
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);再看看interrupted() 方法源码
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);从上面的源码中可以看出 isInterrupted() 方法和 interrupted() 方法实际上底层都是调用 private native boolean isInterrupted(boolean ClearInterrupted) 方法,唯一的区别就是传递的参数,一个是 false,一个是 true,也就是一个不会清除打断标记,另外一个会清除打断标记。
划重点
线程调用 interrupt() 方法并不会真正中断线程,而是让线程具有响应中断的能力,如果你可以在main线程中随意的去停止t1线程,而t1线程却毫无察觉,这是一件很可怕的事情,真正中断的操作应该由t1线程去决定,而不是 main线程或者其他线程。
真确响应线程关闭的方式
任务线程没有sleep/wait
这种场景,比较好处理
import java.time.LocalDateTime;
/**
* @create 2025-10-22 14:15
*/
public class ThreadInterruptTest {
public static void main(String[] args) {
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
// 睡1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 设置线程为中断状态
interruptedThread.interrupt();
}
}
class InterruptedTask implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true) {
if (currentThread.isInterrupted()) {
break;
}
System.out.println("LocalDateTime:" + LocalDateTime.now().toString());
}
}
}任务线程有sleep/wait
这个场景是有坑的,看下面这段示例代码
import java.time.LocalDateTime;
/**
* @create 2025-10-22 14:15
*/
public class ThreadInterruptTest {
public static void main(String[] args) {
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
// 睡1秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 设置线程为中断状态
interruptedThread.interrupt();
}
}
class InterruptedTask implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true) {
if (currentThread.isInterrupted()) {
break;
}
System.out.println("LocalDateTime:" + LocalDateTime.now().toString());
try {
// 睡0.1秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}上面这段代码,任务线程在sleep当中,当被interrupt的时候,会抛出InterruptedException,但是中断标识并没有置为true,导致任务线程还是在无限循环当中,无法响应中断。
正确实现
import java.time.LocalDateTime;
/**
* @create 2025-10-22 14:15
*/
public class ThreadInterruptTest {
public static void main(String[] args) {
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
// 睡1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 设置线程为中断状态
interruptedThread.interrupt();
}
}
class InterruptedTask implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true) {
if (currentThread.isInterrupted()) {
break;
}
System.out.println("LocalDateTime:" + LocalDateTime.now().toString());
try {
// 睡0.1秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
// 在触发InterruptedException异常的同时,JVM会同时把线程的中断标志位清除,所以需要复现中断状态
currentThread.interrupt();
}
}
}
}上面这段实现代码,在捕获InterruptedException后,重新恢复中断标识位,任务线程能够正确响应中断。
自定义中断标识(推荐)
使用jdk自带的方法isInterrupted()需要处理sleep/wait等方法,相比之下,如果自己定义中断标识,就可以避免踩坑
/**
* @create 2025-10-22 14:15
*/
public class ThreadInterruptTest {
public static void main(String[] args) {
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
// 睡0.5秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
interruptedTask.myInterrupted();
}
}
class InterruptedTask implements Runnable {
private volatile boolean sign = false;
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (!sign) {}
}
public void myInterrupted() {
sign = true;
}
}这里要特别注意,自定义的中断标识
sign一定是要使用volatile标识修饰,避免内存可见性问题导致任务线程无法中断