1、interrupt,interrupted和isInterrupted的区别
1.1 interrupt
interrupt()是真正触发中断的方法。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
- sleep、wait、join等方法都统一的对外抛出InterruptedException,所以在调用interrupt方法时是会被唤醒的,然后就会抛出InterruptedException,线程虽然被中断,但此时线程状态状态会被重置为false,下面举例。
- 非nio、synchronized和ReentrantLock.lock(),如果线程处于这些阻塞状态,只能等待io处理完成或者等待到锁之后,程序才能向下执行,从而通过判断中断变量来结束。nio和ReentrantLock.tryLock(long timeout, TimeUnit unit) throws InterruptedException,如果线程处于这些阻塞状态,是可以被唤醒的。你也可以调用reentrantLock.lockInterruptibly() throws InterruptedException方法,它就相当于一个超时设为无限的tryLock方法。其实从方法声明上我们也可以判断出来,因为对外抛出了InterruptedException。
- LockSupport.park()也会被中断,但是不会抛出异常。下面也会举例。
1.2 interrupted和isInterrupted
对于每一个线程,都会有一个Boolean类型中断标志位,该标志位默认false,表示没有被中断.
- isInterrupted() 方法来查看当前的中断标志位的状态,该标志位不影响线程的状态。
- interrupted() 也是可以检查当前线程是否中断,但在调用该方法后,如果标志位为true,该方法会将标志位重置为false。
举例:
public static void test1(){
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//被中断后,抛出异常后,中断标志会被置为false
System.out.println(Thread.currentThread().isInterrupted());
//此时再调用 中断方法,交给线程自己处理
Thread.currentThread().interrupt();
}
//此时中断状态就会变为true,而且调用也不会变更中断标志
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().isInterrupted());
//标志位为true,该方法会将标志位重置为false
System.out.println(Thread.interrupted());
//所以这个会返回false,包括下面的方法也会返回false
System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
});
thread.start();
thread.interrupt();
}
结果
false
true
true
true
false
false
2、LockSupport
-
Thread对象的native实现里有一个成员代表线程的中断状态,我们可以认为它是一个bool型的变量。初始为false。
-
Thread对象的native实现里有一个成员代表线程是否可以阻塞的许可permit,我们可以认为它是一个int型的变量,但它的值只能为0或1。当为1时,再累加也会维持1。初始为0。
LockSupport 中的park和unpark会对这个permit进行处理。
下面将以伪代码的实现来说明park/unpark的实现。
park() {
if(permit > 0) {
permit = 0;
return;
}
if(中断状态 == true) {
return;
}
阻塞当前线程; // 将来会从这里被唤醒
if(permit > 0) {
permit = 0;
}
}
可见,只要permit为1或者中断状态为true,那么执行park就不能够阻塞线程。park只可能消耗掉permit,但不会去消耗掉中断状态。
unpark(Thread thread) {
if(permit < 1) {
permit = 1;
if(thread处于阻塞状态)
唤醒线程thread;
}
}
unpark一定会将permit置为1,如果线程阻塞,再将其唤醒。从实现可见,无论调用几次unpark,permit只能为1。
- park调用后一定会消耗掉permit,无论unpark操作先做还是后做。
- 如果中断状态为true,那么park无法阻塞。
- unpark会使得permit为1,并唤醒处于阻塞的线程。
- interrupt()会使得中断状态为true,并调用unpark。
- sleep() / wait() / join()调用后一定会消耗掉中断状态,无论interrupt()操作先做还是后做。
举例:
1、unpark 不会修改中断状态
public static void test1(){
Thread thread = new Thread(() -> {
LockSupport.park();
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
});
thread.start();
//unpark 不会修改中断状态
LockSupport.unpark(thread);
}
false
false
false
false
false
2、interrupt() 可以中断park()
public static void test1(){
Thread thread = new Thread(() -> {
LockSupport.park();
//此时中断状态就会变为true,而且调用也不会变更中断标志
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().isInterrupted());
//标志位为true,该方法会将标志位重置为false
System.out.println(Thread.interrupted());
//所以这个会返回false,包括下面的方法也会返回false
System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
});
thread.start();
thread.interrupt();
}
true
true
true
false
false
AQS中的parkAndCheckInterrupt()
我们熟悉的可以知道 AQS在入队之后,如果没有获取到锁,会调用parkAndCheckInterrupt(),这里就会阻塞线程,等待被唤醒。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
但是Thread.interrupted()的意义是什么?首先当前线程等着被释放锁调用LockSupport.unpark()或者被其他线程中断,LockSupport.unpark() 唤醒是不会改变当前线程状态的,所以此方法会返回false,回到acquireQueued().死循环继续获取锁。
但是如果被其他线程调用interrupt(),此方法会返回true,但此线程的中断状态也会被变更为false。之所以要变为false,是因为,如果再循环获取锁获取不到时,会再次调用 LockSupport.park(this);此时中断状态为true,是无法阻塞程序的。所以要重置断状态。此时再返回外层循环acquireQueued方法,中断的标志会被 设置 interrupted = true;
selfInterrupt(); 猜测是为了给当前线程设置一个被中断过的标志,由外层线程自己处理。