持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
一、概述
中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序。
二、中断线程方法
interrupt()
描述:该方法用于中断Thread线程,此线程并非当前线程,而是调用interrupt()方法的实例所代表的线程,并不是强制关闭线程,而是将中断标记位设置为true,线程的中断需要在线程内部协作关闭
使用示例
- 通过interrupt()中断正在休眠的线程
/**
* @author lilinchao
* @date 2022-10-08
* @description 通过interrupt() + InterruptedException来中断线程
**/
@Slf4j(topic = "c.Test02")
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
log.debug("start...");
//线程进入睡眠状态 sleep 5s
Sleeper.sleep(5);
log.debug("end...");
}
};
t1.start();
//主线程睡眠 2s 后运行 t1.interrupt 打断正在睡眠的t1线程
Sleeper.sleep(2);
//在线程阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态
t1.interrupt();
}
}
运行结果
14:21:10.443 c.Test02 [t1] - start...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.lilinchao.thread.utils.Sleeper.sleep(Sleeper.java:8)
at com.lilinchao.thread.demo01.Test02$1.run(Test02.java:19)
14:21:12.457 c.Test02 [t1] - end...
说明
- 打断正在运行中的线程并不会影响线程的运行,但如果线程监测到了打断标记为true,可以自行决定后续处理。
- 打断阻塞中的线程(如:线程被
Object.wait,Thread.join和Thread.sleep三种方法之一阻塞)会让此线程产生一个InterruptedException异常,结束线程的运行。但如果该异常被线程捕获住,该线程依然可以自行决定后续处理(终止运行,继续运行,做一些善后工作等等) - 如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出
InterruptedException。
isInterrupted()
描述:用于判断thread的中断状态,不清除中断状态
使用示例
- 停止正在运行中的线程
/**
* @author lilinchao
* @date 2022-10-08
* @description 中断正常运行的线程
**/
@Slf4j(topic = "c.Test09")
public class Test09 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
//无限循环
while (true){
Thread current = Thread.currentThread();
//判断当前线程的中断状态
boolean interrupted = current.isInterrupted();
//如果为true表示线程被中断,执行判断条件,通过break语句退出循环
if (interrupted) {
log.debug("打断状态:{}",interrupted);
break;
}
}
},"t1");
t1.start();
Sleeper.sleep(1);
t1.interrupt();
}
}
运行结果
14:43:19.316 c.Test09 [t1] - 打断状态:true
说明
- 在while()循环中,每次循环,都会通过
isInterrupted()方法判断当前线程的中断状态,如果为true表示线程被interrupt()方法中断,执行判断条件,通过break语句退出循环。以此达到终止正在运行线程的目的。 - 这样做的好处是,即使线程被中断,也不会立即停止该线程的运行,而是可以继续进行后续的善后操作,做到了”优雅的关闭线程“。
interrupted()
描述:该方法为静态方法,判断当前线程的中断状态,并会将中断标记位设置为false,在第二次调用时中断状态会返回false
使用示例
- 通过interrupted()方法停止正在运行中的线程
/**
* @author lilinchao
* @date 2022-10-08
* @description 中断正常运行的线程
**/
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
//无限循环
while (true){
Thread current = Thread.currentThread();
//判断当前线程的中断状态
boolean interrupted = current.interrupted();
//如果为true表示线程被中断,执行判断条件,通过break语句退出循环
if (interrupted) {
log.debug("打断状态1:{}",interrupted);
//再次调用interrupted方法,查看状态标记是否被重置
log.debug("打断状态2:{}",current.interrupted());
break;
}
}
},"t1");
t1.start();
Sleeper.sleep(1);
t1.interrupt();
}
}
运行结果
15:11:51.099 c.Test10 [t1] - 打断状态1:true
15:11:51.101 c.Test10 [t1] - 打断状态2:false
说明
- interrupted()方法返回的是上一次的中断状态,并且会清除该状态,所以连续调用两次,第一次返回true,第二次返回false。
三、两阶段终止模式
Two Phase Termination 在一个线程 T1 中如何“优雅”终止线程 T2?
这里的【优雅】指的是给 T2 一个料理后事的机会。
说明
- 如果线程在睡眠
sleep期间被打断,打断标记是不会变的,为false,但是会抛出异常,此时需要手动设置打断标记为true,阻止线程继续运行; - 如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为
true。 - 处理好这两种情况就可以放心地来料理后事了
错误思路
- 使用线程对象的 stop() 方法停止线程
- stop方法虽然可以强行终止正在运行或挂起的线程,但使用stop方法是很危险的。stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁。
- 使用
System.exit(int)方法停止线程- 目的仅是停止一个线程,但这种做法会让整个程序都停止
使用 isInterrupted
interrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait,还是正常运行
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
TPTInterrupt t = new TPTInterrupt();
t.start();
//异常打断
Thread.sleep(3500);
//正常打断
// Thread.sleep(1000);
log.debug("stop");
t.stop();
}
}
@Slf4j(topic = "c.TPTInterrupt")
class TPTInterrupt{
private Thread thread;
// 启动监控线程
public void start(){
thread = new Thread(() -> {
while(true) {
// 获取当前线程打断状态
Thread current = Thread.currentThread();
// 判断是否打断
if(current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000); // 情况一 异常打断
log.debug("将结果保存"); // 情况二 正常打断
} catch (InterruptedException e) {
// sleep 打断后会清除打断标记,所以要重新设置
current.interrupt();
e.printStackTrace();
}
// 执行监控操作
}
},"监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
运行结果
17:45:27.063 c.TPTInterrupt [监控线程] - 将结果保存
17:45:28.076 c.TPTInterrupt [监控线程] - 将结果保存
17:45:29.089 c.TPTInterrupt [监控线程] - 将结果保存
17:45:29.557 c.Test12 [main] - stop
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lilinchao.thread.demo01.TPTInterrupt.lambda$start$0(Test12.java:35)
at java.lang.Thread.run(Thread.java:748)
17:45:29.558 c.TPTInterrupt [监控线程] - 料理后事
使用停止标记
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
tpt.start();
tpt.start();
Thread.sleep(3500);
log.debug("停止监控");
tpt.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
// 监控线程
private Thread monitorThread;
// 停止标记
private volatile boolean stop = false;
// 判断是否执行过 start 方法
private boolean starting = false;
// 启动监控线程
public void start(){
synchronized (this) {
if (starting) { // false
return;
}
starting = true;
}
monitorThread = new Thread(() -> {
while (true) {
// Thread current = Thread.currentThread();
// 是否被打断
if (stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
}, "monitor");
monitorThread.start();
}
// 停止监控线程
public void stop() {
stop = true;
monitorThread.interrupt();
}
}
运行结果
17:47:34.749 c.TwoPhaseTermination [monitor] - 执行监控记录
17:47:35.757 c.TwoPhaseTermination [monitor] - 执行监控记录
17:47:36.769 c.TwoPhaseTermination [monitor] - 执行监控记录
17:47:37.239 c.Test11 [main] - 停止监控
17:47:37.239 c.TwoPhaseTermination [monitor] - 料理后事
说明
- 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性
四、不推荐的方法
有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
| 方法名 | static | 功能说明 |
|---|---|---|
| stop() | 停止线程运行 | |
| suspend() | 挂起(暂停)线程运行 | |
| resume() | 恢复线程运行 |