interrupt、interrupted 、isInterrupted 详解04

##1 为什么有这三个函数的存在

​ 由于stop关闭线程的不安全性等各种缺点,JDK提供了合理方式便是这三种函数的组合使用

##2 三个函数申明和定义

  • interrupt方法:发起线程中断请求,只是请求并不会真的把线程给中断,实际上是把线程的中断标识设置为了true

    • interrupt的使用

      Thread t = new Thread(){
          @Override
          public void run() {
             while (true){
      
            }
      		}
      };
      t.start();
      Thread.sleep(1000);
      System.out.println(t.isInterrupted());
      t.interrupt();
      System.out.println(t.isInterrupted());
      /*jvm不会退出线程还在死循环执行
      终端输出输出结果是
      false
      true
      */
      
      • 一定要明白中断的含义,中断不是阻塞更不是让线程死掉。它只是告诉计算机出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。中断更多的是指线程标志位的某个状态,中断的处理逻辑需要用户自己来进行定制(你可以选择处理中断,也可以选择不处理),责任交给了用户
      • 不是所状态的线程都能接受中断,比如new或者TERMINATED算发中断信号都无济于事。可以详细参考后面标题4 5 6
    • 发送中断函数interrupt的jdk源码

    //变量申明
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();
    //interrupt方法
    public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
    //interrupt0 本地方法来设置中断位的布尔类型
    private native void interrupt0();
    
  • isInterrupted方法:判断线程的中断标识布尔类型, 不清除标志位.

    public boolean isInterrupted() {
            return isInterrupted(false);//设置为false 不清除标志位
    }
    //调用本地方法来判断中断位的布尔类型 并设置为false
    private native boolean isInterrupted(boolean ClearInterrupted);
    
  • interrupted方法:判断线程的中断标识是否为true还是false,并清除中断符号

    //注意:是static的静态方法
    public static boolean interrupted() {
         return currentThread().isInterrupted(true);//设置为true 清除标志位
    }
    //调用本地方法来判断中断位的布尔类型 ClearInterrupted表示是否需要清除标志位
    private native boolean isInterrupted(boolean ClearInterrupted);
    

##3 案列一:为什么interrupt触发后中断标志位为false

  • 3.1 线程类

    public class Worker implements Runnable{
        @Override
        public void run() {
            System.out.println("Worker started.");
            try {
                System.out.println("Worker normal IsInterrupted: " + Thread.currentThread().isInterrupted());
                Thread.sleep(5000);
                System.out.println("Worker normal IsInterrupted: " + Thread.currentThread().isInterrupted());
            } catch (InterruptedException e) {
                System.out.println("Worker exception IsInterrupted: " + Thread.currentThread().isInterrupted());
            }
            System.out.println("Worker stopped.");
        }
    }
    
  • 3.2 主线程

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Worker());
        t.start();
        Thread.sleep(200);
        t.interrupt();
        System.out.println("Main thread stopped.");
  }
  • 3.3 输出

    Worker started.
    Worker normal IsInterrupted: false
    Worker exception IsInterrupted: false
    Worker stopped.
    Main thread stopped.
    
  • 3.4 详解

    • 主线程在子线程睡眠的时候发起中断请求,线程中断仅仅是设置线程的中断状态位,不会停止线程。用户需要自己去监视线程的状态为并做处理。也就是说线程中断后会抛出InterruptedException的方法,比如这里的sleep,以及Object.wait等方法(就是在监视线程的中断状态),一旦Thread类中线程的中断状态被置为“中断状态”,就会抛出中断异常

    • 由于在sleep的时候监控中断符号被设置true之后,线程会抛出中断异常InterruptedException。该异常一旦被抛出后,中断标志位将会被清除(wait join同理)

       /**
           * @throws  InterruptedException
           *          if any thread has interrupted the current thread. The
           *          <i>interrupted status</i> of the current thread is
           *          cleared when this exception is thrown.
           */
          public static native void sleep(long millis) throws InterruptedException;
      
    • 注意:interrupt()方法发出的中断信号只能被wait() sleep() join()这三个方法捕捉到并产生中断

      可以看到除了sleep之外,wait/join抛出异常都清除中断标志位

        * @throws  InterruptedException if any thread interrupted the
           *             current thread before or while the current thread
           *             was waiting for a notification.  The <i>interrupted
           *             status</i> of the current thread is cleared when
           *             this exception is thrown.
           * @see        java.lang.Object#notify()
           * @see        java.lang.Object#notifyAll()
           */
          public final native void wait(long timeout) throws InterruptedException;
      
        * @throws  InterruptedException
           *          if any thread has interrupted the current thread. The
           *          <i>interrupted status</i> of the current thread is
           *          cleared when this exception is thrown.
           */
          public final void join() throws InterruptedException {
              join(0);
          }
      

4 线程集中状态下对中断的反应

  • 线程常见的几种状态

    ​ RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度

    ​ WAITING:线程在等待某个条件

    ​ TIMED_WAITING: 线程等待超时

    ​ BLOCKED: 线程在等待锁,试图进入同步块

    ​ NEW: 线程未启动

    ​ TERMINATED: 已结束

  • 线程在这几种状态下对中断的反应和处理

    • RUNNABLE:中断只会设置线程的中断标志位,在当前的线程被外界触发中断后,可以在线程中使用wile不断监控线程的中断位是否变化从而控制线程的结束

      public class InterruptRunnableDemo extends Thread {
          @Override
          public void run() {
              while (!Thread.currentThread().isInterrupted()) {
                  // ... 单次循环代码
              }
              System.out.println("done ");
          }
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new InterruptRunnableDemo();
              thread.start();
              Thread.sleep(1000);
              thread.interrupt();
          }
      }
      
    • Waiting: 这种线程状态只有在线程执行了方法join和wait会进入waiting状态。对线程对象调用interrupt()会使得该线程抛出InterruptedException,中断标志位会被清空(线程的中断标志位会由true重置为false

    • Timed_waiting:方法wait、sleep、join会让方法进入Timed_waiting状态。对线程对象调用interrupt()会使得该线程抛出InterruptedException,中断标志位会被清空(线程的中断标志位会由true重置为false

    • Blocked:interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,interrupt()并不能使一个在等待锁的线程真正”中断”。

    • new/terminate:调用interrupt()对它没有任何效果

5 IO操作对中断的反应

  • 实现此InterruptibleChannel接口的通道是可中断:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 方法,这将导致该通道被关闭,并且已阻塞线程接将会收到ClosedByInterruptException,并且设置已阻塞线程的中断状态。

  • 如果线程阻塞于Selector调用,则线程的中断标志位会被设置,同时,阻塞的调用会立即返回

  • InputStream的Read不可中断,流中没有数据会阻塞,中断后会依然阻塞(是runnable状态 但不相应中断)

    public class InterruptReadDemo {
        private static class A extends Thread {
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted()){
                    try {
                        System.out.println(System.in.read())//wait input
                    } catch (IOException e) {
                        e.printStackTrace();
                    }    
                }
                System.out.println("exit");
            }
        }
        public static void main(String[] args) throws InterruptedException {
            A t = new A();
            t.start();
            Thread.sleep(100);
            t.interrupt();//线程不会结束 一直等待IO输入
        }
    }
    

##6 为什么线程可以在阻塞的时候捕获中断异常并处理(wait、sleep、join)

  • 以下是三个方法的声明

     public final native void wait(long var1) throws InterruptedException;
     public static native void sleep(long millis) throws InterruptedException;
     public final synchronized void join(long millis)
        throws InterruptedException {
            long base = System.currentTimeMillis();
            long now = 0;
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }
    
  • 阻塞和中断的区别

    • 阻塞:表示线程的一种状态,线程是不占用CPU的(你的代码在执行过程中,在某个地方暂停了)
    • 中断:计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。中断更多的是指线程标志位的某个状态,中断的处理逻辑需要用户自己来进行定制(你可以选择处理中断,也可以选择不处理),责任交给了用户。
  • 哪些方法可以捕捉中断

    ​ 一般说来,当可能阻塞的方法声明中有抛出 InterruptedException 则暗示该方法是可中断的,如 BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep 等。

##7 案列二:如何能使中断后的中断标志还是true

  • 子线程

    public class Worker1 implements Runnable{
        @Override
        public void run() {
            System.out.println("Worker1 started.");
            try {
                System.out.println("Worker1 sleep started.");
                Thread.sleep(5000);
                System.out.println("Worker1 sleep end.");
            } catch (InterruptedException e) {
                System.out.println("Worker1 exception 1 IsInterrupted: " + Thread.currentThread().isInterrupted());
                System.out.println("Worker1 exception 1 interrupted: " + Thread.currentThread().isInterrupted());
                Thread.currentThread().interrupt();
               // Thread.currentThread().interrupt();
                //再次调用interrupt方法中断自己,将中断状态设置为“中断”
                System.out.println("Worker1 exception 2 IsInterrupted: " + Thread.currentThread().isInterrupted());
                System.out.println("Worker1 exception 2 interrupted: " + Thread.interrupted());
                System.out.println("Static Call: " + Thread.interrupted());//clear status
            }
            System.out.println("Worker stopped.");
        }
    }
    
  • 主线程:不变

     Thread t = new Thread(new Worker1());
            t.start();
            Thread.sleep(200);
            t.interrupt();
            System.out.println("Main thread stopped.");
    
  • 输出

    Worker1 started.
    Worker1 sleep started.
    Main thread stopped.
    Worker1 exception 1 IsInterrupted: false
    Worker1 exception 1 interrupted: false
    Worker1 exception 2 IsInterrupted: true
    Worker1 exception 2 interrupted: true
    Static Call: false
    Worker stopped.
    
  • 详解

    • 由于抛出异常InterruptedException之后,中断位标志已经被清除成false,所以前两次打印是false(标志位为false是因为抛出异常 标志位被清除)
    • 而再次调用线程的中断,可以将中断位置为true

##8 什么场景该使用要重置中断标志位

  • 场景:sleep、wait、join三种方法被用在了一个没有申明抛出异常InterruptedException的方法里,但是又想告诉上层调用者这里发生了中断,就只能在catch里面进行重置。

  • 线程代码

    public class TaskRunner implements Runnable{
        private BlockingQueue<Task> queue;
        public TaskRunner(BlockingQueue<Task> queue) {
            this.queue = queue;
        }
        @Override
        public void run() {
            try {
                while (true) {
                    Task task = queue.take();
                    task.execute();
                }
            } catch (InterruptedException e) {
                // Restore the interrupted status
                Thread.currentThread().interrupt();
            }
        }
    }
    
  • LinkedBlockingQueue的take方法的源码

    public E take() throws InterruptedException {
            E x;
            int c = -1;
            final AtomicInteger count = this.count;
            final ReentrantLock takeLock = this.takeLock;
            takeLock.lockInterruptibly();
            try {
                while (count.get() == 0) {
                    notEmpty.await();
                }
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            } finally {
                takeLock.unlock();
            }
            if (c == capacity)
                signalNotFull();
            return x;
        }
    
  • 详解

    • 当TaskRunner线程被中断被捕获并抛出异常InterruptedException
    • catch抓住异常,该run方法没有将该异常抛出而是被捕获,倘若上层代码想通过中断标志位来显示线程是否被中断,就需要在这个地方再次中断使之为true,之后上层监控中断标志位来判断是否被中断
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值