1. interrupt打断线程
1.1 打断sleep,wait,join(阻塞状态)的线程
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
log.debug("sleep;;;");
try {
Thread.sleep(5000); // sleep,join,wait
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
结果:
22:01:29.967 [t1] DEBUG com.multiThread.TestInterrupt1 - sleep;;;
22:01:30.963 [main] DEBUG com.multiThread.TestInterrupt1 - interrupt
22:01:30.963 [main] DEBUG com.multiThread.TestInterrupt1 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.yhx.toali.multiThread.TestInterrupt1.lambda$main$0(TestInterrupt1.java:16)
at java.base/java.lang.Thread.run(Thread.java:830)
可以看出阻塞状态的线程,被打断后会抛出一个异常;同时阻塞状态的线程被打断后,会将打断标记置为假。
1.2 打断正常执行的线程
给定一个死循环,如何打断这个死循环:
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
while (true) {
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
执行结果:可以看到程序并没有停止,还在一直执行
我们可以通过获取打断状态来停止这个线程:
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("被打断,退出");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
执行结果:
2. 两阶段终止模式
在一个线程t1中如何“优雅”地终止线程t2?这里的“优雅”指的是给t2一个料理后事的机会。
2.1 错误思路:
- 使用线程对象的stop()方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁
- 使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但是这种做法会让整个程序都停止
2.2 实现思路:
2.3 代码展示:
public class TestInterrupt3 {
public static void main(String[] args) throws InterruptedException{
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.stop();
}
}
@Slf4j
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) { // 获取终止状态
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000); //情况1
log.debug("执行监控记录"); // 情况2
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
current.interrupt();
}
}
});
monitor.start();
}
public void stop() {
monitor.interrupt();
}
}
执行结果:
22:44:24.932 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
22:44:25.947 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
22:44:26.948 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.yhx.toali.multiThread.TwoPhaseTermination.lambda$start$0(TestInterrupt3.java:36)
at java.base/java.lang.Thread.run(Thread.java:830)
22:44:27.432 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 料理后事
如果注释掉catch中的interrupt,查看执行结果:可以看到执行没有停止,这是因为:当线程在执行阻塞操作时(比如调用sleep/wait/yield/join方法时)调用了interrupt()会抛出InterruptException异常并且将该线程的”中断”标志位清空,会将线程的中断标志重新设置为false。所以在需要在线程是打断状态下重新调用interrupt方法,将线程状态设置为打断,触发料理后事方法。
3. interrupted()和isInterrupted()区别:
3.1 修改上述料理后事的代码:
if (current.isInterrupted()) {
log.debug("料理后事");
System.out.println(current.isInterrupted());
break;
}
结果:
11:52:43.814 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
11:52:44.823 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
11:52:45.831 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.yhx.toali.multiThread.TwoPhaseTermination.lambda$start$0(TestInterrupt3.java:42)
at java.base/java.lang.Thread.run(Thread.java:830)
11:52:46.316 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 料理后事
true
3.2 修改上述current.isInterrupted()
为monitor.interrupted()
:
if (monitor.interrupted()) {
log.debug("料理后事");
System.out.println(current.isInterrupted());
break;
}
结果:
11:54:41.316 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
11:54:42.349 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
11:54:43.356 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.yhx.toali.multiThread.TwoPhaseTermination.lambda$start$0(TestInterrupt3.java:42)
at java.base/java.lang.Thread.run(Thread.java:830)
11:54:43.793 [Thread-0] DEBUG com.yhx.toali.multiThread.TwoPhaseTermination - 料理后事
false
3.3 结论:
- 他们都是获取打断线程状态,但是isInterrupted不会清除打断标记,而interrupted会。
也就是说调用interrupted
方法会返回当前线程中断状态,同时清除中断状态(置为false) - Thread.interrupted() 线程静态方法,
Thread.currentThread().isInterrupted() 线程实例方法
3.4 源码查看:
interrupted:
public static boolean interrupted() {
Thread t = currentThread();
boolean interrupted = t.interrupted;
// We may have been interrupted the moment after we read the field,
// so only clear the field if we saw that it was set and will return
// true; otherwise we could lose an interrupt.
if (interrupted) {
t.interrupted = false;
clearInterruptEvent();
}
return interrupted;
}
isInterrupted:
public boolean isInterrupted() {
return interrupted;
}
可以看到他们拿到的都是Thread的成员变量interrupted的值,不过interrupted
拿到之后还会设置其为fasle
private volatile boolean interrupted;