【Java并发编程】 interrupt 方法详解
文章目录
- 【Java并发编程】 interrupt 方法详解
- 1.介绍
- 2.打断阻塞状态线程
- 3.打断正常状态的线程
- 4.两阶段终止模式
- 5.打断 park 线程
1.介绍
程序中,有些线程的终端需要外部干预,比如有线程存在while(true)循环,或者一些阻塞操作,比如sleep
、wait
、join
等。
终端线程的方式,如果直接使用 stop
、suspend
等烦啊,对程序来说是不太严谨的,这些方法类似于直接杀死线程,可能会造成数据问题。
为了更好的解决这些问题,我们可以使用interrupt
方法,调用该方法就相当于告诉线程你要被打断了,赶快准备结束你的线程,具体什么时候结束,你自己决定,我只是给你传个话。这种方式就比stop
这种强行让你结束好多了。
2.打断阻塞状态线程
sleep,wait,join这几个方法都会让线程进入阻塞状态,该状态下,如果线程被打断,则会清空打断状态(被打断时为true),并抛出异常。以sleep
为例:
public class Main {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
System.out.println(t1.isInterrupted());
}
}
结果:打断标记为false
,本来被打断后打断标记应该为true
,但是因为当阻塞线程被打断后会抛出异常,并且清楚打断标记。 为啥会这样呢?
一个正在阻塞的线程被打断,因为该线程还在阻塞,不知道到底发生了什么,突然被打断就会抛异常来停止当前的阻塞状态,以应对外面发生的变化。也就是响应中断。
3.打断正常状态的线程
public class Main {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
System.out.println("被打断");
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
System.out.println(t1.isInterrupted());
}
}
我们通过运行结果可以发现,打断正常线程是打断标记会置为true,然后线程可以继续执行,所以就给了打断线程处理后事的时间,自己选择何时结束线程。这样做的目的是 对之前的线程的数据保存现场或者处理,如果直接中断就会出现很多问题,比如一个线程拿着锁(手动上锁下锁)突然被中断,那么该锁就一直不会被释放,其他线程就会一直等待,造成死锁。
4.两阶段终止模式
提出问题
如果在一个线程中“体面”的终止另一个线程,也就是给终止线程一个处理后事的机会。
错误的解决思路
1.使用stop
方法终止线程
这种思路是错误的 因为Thread.stop()方法在结束线程时,会直接终止线程,并且会立即释放这个线程所有的锁,而这些锁恰恰是用来维持对象一致性的。如果此时,写入线程写入的数据正写到一半,并强行终止,那么对象就会被写坏,同时由于锁已经释放,另外一个等待该锁的线程就会顺理成章的对到了这个不一致的对象。
2.使用System.exit(int)方法终止线程
这个方法会让整个程序都停止,而我们的目的仅仅是停止一个线程
正确的解决思路
用interrupt方法,当陷入阻塞时 中断线程抛出异常 可以处理后事,当中断正常状态线程时 可以根据中断标记 来处理后事
public class TPTInterrupt {
private Thread thread;
public void start() {
thread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
System.out.println("料理后事");
break;
}
try {
Thread.sleep(1000);
System.out.println("保存数据");
} catch (InterruptedException e) {
e.printStackTrace();
// 将打印标记置为true
current.interrupt();
}
// 执行监控操作
}
}, "监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
测试
public class TPTInterruptDemo {
public static void main(String[] args) throws InterruptedException {
TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
System.out.println("stop");
t.stop();
}
}
结果
保存数据
保存数据
保存数据
stop
料理后事
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at org.example.juc.TPTInterrupt.lambda$start$0(TPTInterrupt.java:19)
at java.lang.Thread.run(Thread.java:750)
如果线程正在正常执行,那么如果线程被打断,打断标志就为true,就可以处理后事了,如果线程正在阻塞状态,那么如果线程被打断,就需要再次打断该线程使标志为true,因为阻塞异常被打断会抛出异常并重置为false,这样才能给线程处理后事的机会并且知道线程在阻塞状态被打断了。
5.打断 park 线程
**打断 park 线程,不会清空打断状态 **
public class parkDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打断状态: " + Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
}
}
结果
park...
unpark...
打断状态: true
如果打断标志已经是 true,则park会失效
public class parkDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("park...");
LockSupport.park();
System.out.println("打断状态: " + Thread.currentThread().isInterrupted());
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
}
}
结果
park...
打断状态: true
park...
打断状态: true
park...
打断状态: true
park...
打断状态: true
park...
打断状态: true