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,不保存数据直接中断?)

  • 某个操作超过了一定的执行时间限制需要中止时;

  • 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;

  • 一组线程中的一个或多个出现错误导致整组都无法继续时;

    因为中断是一种协同机制,提供了更优雅中断方式,也提供了更多的灵活性,所以当遇到如上场景等,我们就可以考虑使用中断机制了

使用中断机制有哪些注意事项

其实使用中断机制无非就是注意上面说的两项内容:

  1. 中断标识
  2. InterruptedException

前人已经将其总结为两个通用原则:

原则-1

如果遇到的是可中断的阻塞方法, 并抛出 InterruptedException,可以继续向方法调用栈的上层抛出该异常;如果检测到中断,则可清除中断状态并抛出 InterruptedException,使当前方法也成为一个可中断的方法

原则-2

若有时候不太方便在方法上抛出 InterruptedException,比如要实现的某个接口中的方法签名上没有 throws InterruptedException,这时就可以捕获可中断方法的 InterruptedException 并通过 Thread.currentThread.interrupt() 来重新设置中断状态。(前文interrupt()方法的最后一个例子)

posted @ 2021-07-27 21:33  Zoran0104  阅读(65)  评论(0编辑  收藏  举报