何为中断?
计算机的世界里处处都有中断,任何工作都离不开中断,可以说整个计算机系统就是由中断来驱动的。那么什么是中断?简单来说就是CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断!
响应中断与非响应中断的区别
每个线程都有一个boolean类型的中断状态,当中断线程时,这个线程的中断状态将被设置为true,在Thread中包含了中断线程以及查询线程中断的方法,如下:
public class Thread{
public void interrupt(){.......}
public boolean isInterrupted(){......}
public static boolean interrupted(){......}
}
复制代码
interrupt()
方法能中断目标线程,而isInterrupted()
方法能返回目标线程的中断状态。静态的interrupted()
方法将清除当前的中断状态并返回它之前的值,这也是清除中断状态的唯一方法。
调用interrupt()
方法并不意味着立即停止目标线程正在执行的工作,而只是传递了请求中断的消息。
下面我们来看一个例子来理解下这3个方法:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
boolean isInterrupted = Thread.currentThread().isInterrupted();
System.out.println(isInterrupted);
if (isInterrupted) {
System.out.println("hao");
}
System.out.println(Thread.currentThread().getName() + " threadOne isInterrupt1 " + Thread.currentThread().isInterrupted());
while (!Thread.interrupted()){
System.out.println("--------");
}
System.out.println(Thread.currentThread().getName() + " threadOne isInterrupt2 " + Thread.currentThread().isInterrupted());
}
}, "t1");
//启动线程
threadOne.start();
//设置中断标志
threadOne.interrupt();
System.out.println("interrupt 执行完毕。。。。。");
threadOne.join();
System.out.println("main over... ");
System.out.println(Thread.currentThread().getName() + " isInterrupted3:" + threadOne.isInterrupted());
}
}
复制代码
输出结果:
interrupt 执行完毕。。。。。
true
hao
t1 threadOne isInterrupt1 true
t1 threadOne isInterrupt2 false
main over...
main isInterrupted3:false
复制代码
线程执行的顺序不一样打印的结果也不一样!
false
t1 threadOne isInterrupt1 false
--------
--------
--------
--------
--------
--------
--------
interrupt 执行完毕。。。。。
t1 threadOne isInterrupt2 false
main over...
main isInterrupted3:false
复制代码
第三种输出结果
false
t1 threadOne isInterrupt1 true
t1 threadOne isInterrupt2 false
interrupt 执行完毕。。。。。
main over...
main isInterrupted3:false
复制代码
main线程打印的 isInterrupted3一直都是false,因为没有线程去中断main线程,这里先忽略,只看t1线程;好好揣摩一下上面3种打印的结果就能明白这3个方法的作用;
当线程在调用wait()
,sleep()
,和join()
方法的时候,这个时候如果收到中断请求(调用了interrupt()
方法)或者在开始执行时发现某个已被设置好的中断状态时,将抛出一个异常!
public class InterruptTest {
@SneakyThrows
public static void main(String[] args) {
Thread threadOne = new Thread(new Runnable() {
@Override
@SneakyThrows
public void run() {
while (true) {
System.out.println("+++++++++++");
Thread.sleep(3000);
System.out.println("----------");
}
}
}, "t1");
InterruptTest t = new InterruptTest();
threadOne.start();
Thread.sleep(200);
threadOne.interrupt();
}
}
复制代码
打印结果:
+++++++++++
Exception in thread "t1" java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.haoxy.use.lock.reenlock.InterruptTest$1.run(InterruptTest.java:25)
at java.lang.Thread.run(Thread.java:748)
复制代码
上面我们对Thread中的interrupt(),isInterrupted(),interrupted()
有所了解了;下面我们看看什么是响应中断和非响应中断。
AQS中的响应中断与非响应中断
其实上面抛出异常就是响应中断的一种方式;我们以Lock为例:
public static void main(String[] args) {
Lock lock = new ReentrantLock();
//这里先加锁的目的是 让下面的lock.lockInterruptibly()方法能进入到doAcquireInterruptibly()方法中
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//注意这里使用的是lockInterruptibly()方法-响应中断
lock.lockInterruptibly();
System.out.println("lockInterruptibly....");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted.");
e.printStackTrace();
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
//调用interrupt()方法将其中断
t1.interrupt();
Thread.sleep(5000);
}
复制代码
打印结果
t1 interrupted.
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.haoxy.use.lock.reenlock.InterruptTest$3.run(InterruptTest.java:92)
at java.lang.Thread.run(Thread.java:748)
复制代码
这里调用了lock.lockInterruptibly()
尝试去获取锁,在尝试获取锁的过程中调用了t1.interrupt()
方法将t1线程设置中断标志,跟踪源码当代码来到doAcquireInterruptibly()
方法中:
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return;
}
// 1 判断是否真的需要挂起,如果需要挂起就会调用parkAndCheckInterrupt()方法将其挂起
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
这里重点是1处的代码shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()
,其他代码我们已经在JUC AbstractQueuedSynchronizer(AQS)源码-加解锁分析中详细的讲解了,这里就不在过多的赘述!
因为在调用lock.lockInterruptibly()
方法之前在main线程中调用了lock.lock()
方法,所以这里是要将线程挂起等待的。所以执行了parkAndCheckInterrupt()
方法中的LockSupport.park(this)
方法将线程挂起!挂起不是我们的重点,我们的重点是在挂起之后醒来的时候,整个main方法执行结束,lock.lock()
结束,主线程释放锁,这个时候就会轮到线程t1去获取锁;t1线程就会在它挂起的地方醒来:
private final boolean parkAndCheckInterrupt() {
//挂起的地方
LockSupport.park(this);
//醒来继续向下执行
return Thread.interrupted();
}
复制代码
当醒来的继续向下执行Thread.interrupted()
,在执行Thread.interrupted()
方法的时候t1.interrupt()
方法已经执行了,也就是中断t1线程;Thread.interrupted()
返回true
;那接下来就是throw new InterruptedException()
;t1线程就被中断获取锁了;这就是Lock响应中断获取锁的过程;
有响应中断,当然也会有非响应中断:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
如果你看过我前面的文章你对上面的代码肯定很眼熟!
这个和响应中断获取锁的不同的地方在:
//不响应中断
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
interrupted = true;
}
//响应中断
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
throw new InterruptedException();
}
复制代码
不响应中断就是简单的设置一下标志位让 interrupted = true
;
但是这里有一个问题一直也在困扰我,将 interrupted = true
也就是acquireQueued(final Node node, int arg)
方法返回了true
,那接下来就会执行selfInterrupt();
方法;
static void selfInterrupt() {
//再次将当前线程设置为中断状态
Thread.currentThread().interrupt();
}
复制代码
selfInterrupt();
方法中调用了interrupt()
方法;目的是什么呢?我个人觉得因为在线程醒来的时候在parkAndCheckInterrupt()
方法里面调用了Thread.interrupted()
方法将线程的标志改变了,当调Thread.interrupted()
虽然返回了true
,但是将中断标志改为了false
,所以要在selfInterrupt()
方法中调用Thread.currentThread().interrupt()
将当前线程设置为中断状态(复位);也就是还原用户的标志,用户判断线程状态自行决定怎么处理;