zoukankan      html  css  js  c++  java
  • Java应用级别的线程中断—interrupt&isInterrupted&interrupted的区别

    线程的打断 interrupt()

    Thread#interrupt() 这个方法仅仅是给线程设置一个打断标记,并不是它字面上的意思,更不是说你调用了这个方法,线程就立即停止了。

    线程的打断需要应用程序的响应,如果没有响应,打断就不会被执行。

    示例1

    import java.util.concurrent.TimeUnit;
    
    public class Main {
    
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("Thread start...");
          while (true) {
            System.out.println("running");
          }
        });
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
      }
    }
    

    运行该示例,你会发现尽管调用了 thread.interrupt() 但是线程还是在一直输出 running,这就表明线程并没有像我们期望的那样被打断执行!

    判断线程是否被打断 isInterrupted

    Thread#isInterrupted() 该方法可以判断线程对象 thread 的打断标记是否被置位,支持多次判断,且结果一致。

    示例2

    import java.util.concurrent.TimeUnit;
    
    public class Main {
    
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("Thread start...");
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
          }
          System.out.println("Thread finish...");
        });
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
      }
    }
    

    应用程序响应打断

    sleep 被打断

    比如 sleep 方法就是可以响应打断的。

    示例3

    public class Main {
    
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("Thread start...");
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            try {
              TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          System.out.println("Thread finish...");
        });
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
      }
    }
    

    sleep 会响应中断,把线程从睡眠状态立即唤醒过来,同时会把中断标记位重置,因此此时线程中的 while 循环还会继续执行下去。
    因此,你需要在修改睡眠的这部分代码:

    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
    }
    

    park 被打断

    park 方法会让线程陷入等待 WAITING,但是线程的打断会让 park 方法醒过来

    示例4

    public class Main {
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("Thread start...");
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            LockSupport.park();
            System.out.println("Thread wakeup...");
          }
          System.out.println("Thread finish...");
        });
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
      }
    }
    

    park() 方法响应线程打断,线程被唤醒并继续执行,但是打断标记不会被重置。

    获取并重置打断标记 interrupted

    Thread.interrupted() 返回当前线程是否被设置了打断标记,且重置线程为未打断状态。

    示例5

    public class Main {
    
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("Thread start...");
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            LockSupport.park();
            System.out.println("Thread wakeup...");
            if (Thread.interrupted()) {
              System.out.println("本次线程因 interrupt 被强行唤醒");
            } else {
              System.out.println("本次线程被 unpark 唤醒");
            }
          }
          System.out.println("Thread finish...");
        });
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
        TimeUnit.MILLISECONDS.sleep(100);
        LockSupport.unpark(thread);
      }
    }
    

    打断在源码中的应用

    ReentrantLock 有两类加锁的 API
    第一种是 lock, 这种从结果上看就和 synchronized 一样是不可以被打断的

    public void lock() {
        sync.lock();
    }
    

    另外一种是 lockInterruptibly,这种在加锁时,如果发生线程打断,是会抛出异常的

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    

    lockInterruptibly

    doAcquireInterruptibly 是 acquireInterruptibly 中调用的。

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        // 在排队队列的末尾新增一个结点
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                // 获取新增加的结点的前置结点
                final Node p = node.predecessor();
                // 条件一:p == head
                // 条件成立:表示该前置结点是队列的第一个结点,代表当前获取到锁且正在运行的线程,但是该 Node 对象的成员变量 thread = null
                // 条件不成立:前置结点是一个正在等待锁的结点,因此跳出循环
                // 条件二:tryAcquire(arg) 尝试进行一次获取锁的操作
                // 条件成立:前一个线程刚好释放锁了,因此此时当前线程加锁成功
                if (p == head && tryAcquire(arg)) {
                    // 设置当前结点 node 为等待队列的头结点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 如果线程是被中断唤醒的,lockInterruptibly 会抛出一个 InterruptedException
                    // 这个异常就是从此处抛出的
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    parkAndCheckInterrupt 这个方法应用了 Thread.interrupted() 方法

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        // 返回当前线程的打断状态,并且重置当前线程的打断状态
        // Thread.interrupted() 这个方法可以帮助我们判断线程能从 WAITING 状态变为 RUNNABLE 状态
        // 是因为 LockSupport#unpark(Thread thread) 方法的唤醒还是来自于 Thread#interrupt() 的打断
        return Thread.interrupted();
    }
    

    lock 方法中的 selfInterrupt()

    lock() 方法不响应打断,通过 selfInterrupt() 重设线程打断状态,然后交由程序调用者来响应打断行为。
    在此情况下,效果就类似于 synchronized ,加锁过程不会被打断。

    public final void acquire(int arg) {
        // 条件一:!tryAcquire(arg)
        // 条件成立:当前线程尝试获取锁失败
        // 条件不成立:当前线程成功获取独占锁
        // 条件二:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        // 条件成立:在线程 WAITING 状态下发生打断
        // 条件不成立:线程是被正常唤醒的,没有发生打断来强制唤醒的情况
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 线程是被打断唤醒的,且经过自旋获取到锁了,但是 lock 过程没有响应打断
            // 因此重新设置线程打断标记,交给调用者去响应中断
            selfInterrupt();
    }
    

    selfInterrupt 运用了 Thread.currentThread().interrupt()

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    
  • 相关阅读:
    python正则表达式
    pyperclip模块
    python画国旗
    linux6.5禁用telnet
    限制用户su到root
    按系统日期生成目录
    微软windows logo配色rgb
    fastclick使用与 fastclick ios11.3相关bug原因(ios输入框点击变得不灵敏,ios input失焦后,页面上移,点击不了)
    vue iframe嵌套页面高度自适应 (ios 宽度扩大的bug , ios展示比例问题)
    iOS设备 微信h5页面回退 内容不刷新的问题
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/java_Thread_interrupt_isInterrupted_and_interrupted.html
Copyright © 2011-2022 走看看