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();
}