java if interrupted_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();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值