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();
    }
    
  • 相关阅读:
    (转载)C++ string中find() ,rfind() 等函数 用法总结及示例
    UVA 230 Borrowers (STL 行读入的处理 重载小于号)
    UVA 12100 打印队列(STL deque)
    uva 12096 The SetStack Computer(STL set的各种库函数 交集 并集 插入迭代器)
    uva 1592 Database (STL)
    HDU 1087 Super Jumping! Jumping! Jumping!
    hdu 1176 免费馅饼
    HDU 1003 Max Sum
    转战HDU
    hust 1227 Join Together
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/java_Thread_interrupt_isInterrupted_and_interrupted.html
Copyright © 2011-2022 走看看