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();
}
原文地址:https://www.cnblogs.com/kendoziyu/p/java_Thread_interrupt_isInterrupted_and_interrupted.html