JUC(2)线程中断机制
如何停止、中断一个运行中的线程?
- 首先一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
- 其次,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。
- 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
- 中断协商机制并不是立刻马上停止一个线程。
- Java 的每个线程对象里都有一个 boolean 类型的标识,代表是否有中断请求,可你寻遍 Thread 类你也不会找到这个标识,因为这是通过底层 native 方法实现的。
- 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设为true
- 每个线程对象中都有一个标识,用于标识线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用
如何使用中断标识停止线程
-
在需要中断的线程中不断监听中断状态,一旦发生中断,就执行型对于的中断处理业务逻辑
-
修改状态
-
停止程序的运行
-
…
-
三种中断标识停止线程的方式
-
通过volatile
static volatile boolean isStop = false; public static void m1() { new Thread(() -> { while (true) { if (isStop) { System.out.println("-----isStop = true,程序结束。"); break; } System.out.println("------hello isStop"); } }, "t1").start(); //暂停几秒钟线程 try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} new Thread(() -> { isStop = true; }, "t2").start(); }
-
通过AtomicBoolean
static AtomicBoolean atomicBoolean = new AtomicBoolean(false); public static void m2() { new Thread(() -> { while (true) { if (atomicBoolean.get()) { System.out.println("-----atomicBoolean.get() = true,程序结束。"); break; } System.out.println("------hello atomicBoolean"); } }, "t1").start(); //暂停几秒钟线程 try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} new Thread(() -> { atomicBoolean.set(true); }, "t2").start(); }
-
通过Thread类自带的中断的API方法实现
public class InterruptDemo { public static void m3() { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("-----isInterrupted() = true,程序结束。"); break; } System.out.println("------hello Interrupt"); } }, "t1"); t1.start(); //暂停几秒钟线程 try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} new Thread(() -> { t1.interrupt();//修改t1线程的中断标志位为true }, "t2").start(); } }
-
interrupt()
interrupt() 方法是唯一一个可以将上面提到中断标志设置为 true 的方法,从这里可以看出,这是一个 Thread 类 public 的对象方法,所以可以推断出任何线程对象都可以调用该方法,进一步说明就是可以一个线程 interrupt 其他线程,也可以 interrupt 自己。其中,中断标识的设置是通过 native 方法 interrupt0 完成的
如果这个线程因为被wait()、join()、sleep()方法阻塞时被打断(interupt),会清空中断标识并抛出InterruptedException(故而我们调用以上方法时强制我们try…catch…处理或抛出)其实被中断抛出 InterruptedException 的远远不止这几个方法,比如:
反向推理,这些可能阻塞的方法如果声明有 throws InterruptedException
, 也就暗示我们它们是可中断的
由于他还会带来一个副作用就是清空中断标志位,中断标识也就重置为false,导致无限循环无法听下来,故而往往我们写代码时,会在catch时再调用一次interrupt方法确保线程被中断
public class InterruptDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("被中断,88");
break;
}
System.out.println("hello");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}, "t1");
t1.start();
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
t1.interrupt();
}
}
总的来说,当对一个线程,调用 interrupt() 时:
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。
- 被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
- 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
isInterrupted()
该方法返回中断标识的结果:
- true:线程被中断
- false:线程没被中断或被清空了中断标识
拿到这个标识后,线程就可以判断这个标识来执行后续的逻辑了。
interrupted()
其实和上面的 isInterrupted() 方法差不多,两个方法都是调用 private
的 isInterrupted() 方法, 唯一差别就是会清空中断标识
因为调用该方法,会返回当前中断标识,同时会清空中断标识:
Thread.currentThread().isInterrupted(); // true
Thread.interrupted() // true,返回true后清空了中断标识将其置为 false
Thread.currentThread().isInterrupted(); // false
Thread.interrupted() // false
那么现实中,这个方法有什么用呢?
当你可能要被大量中断并且你想确保只处理一次中断时,就可以使用这个方法了
该方法在 JDK 源码中应用也非常多,比如:
中断机制的使用场景
通常,中断的使用场景有以下几个
-
点击某个桌面应用中的关闭按钮时(比如你关闭 IDEA,不保存数据直接中断?)
-
某个操作超过了一定的执行时间限制需要中止时;
-
多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
-
一组线程中的一个或多个出现错误导致整组都无法继续时;
因为中断是一种协同机制,提供了更优雅中断方式,也提供了更多的灵活性,所以当遇到如上场景等,我们就可以考虑使用中断机制了
使用中断机制有哪些注意事项
其实使用中断机制无非就是注意上面说的两项内容:
- 中断标识
- InterruptedException
前人已经将其总结为两个通用原则:
原则-1
如果遇到的是可中断的阻塞方法, 并抛出 InterruptedException,可以继续向方法调用栈的上层抛出该异常;如果检测到中断,则可清除中断状态并抛出 InterruptedException,使当前方法也成为一个可中断的方法
原则-2
若有时候不太方便在方法上抛出 InterruptedException,比如要实现的某个接口中的方法签名上没有 throws InterruptedException,这时就可以捕获可中断方法的 InterruptedException 并通过 Thread.currentThread.interrupt() 来重新设置中断状态。(前文interrupt()方法的最后一个例子)