前几篇介绍AQS源码都是基于正常流程来分析,把主要流程走了一遍,但是在看源码时发现好多逻辑都没走到,其中最重要的当属线程的中断以及节点在什么情况下会取消排队,这篇文章我们还是拿具体例子来分析一下。
首先简单说一下线程的中断。**java线程中,中断只是一个状态,中断线程只是设置了中断状态,并不是立刻将线程中断停止。**响应中断是取决于你自己代码逻辑中是否会判断中断状态,如果你不判断中断状态,那么设置了中断其实也没有影响,只是告诉你线程被中断过,让开发者可以自主选择处理中断的逻辑。下面简单介绍下几个重要方法
class Thread implements Runnable {
/**
* 实例方法 中断线程,其实是将线程的中断状态设置为true
*/
public void interrupt() {}
/**
* 静态方法 让线程本身判断是否被中断过,返回线程的中断状态,并且重置中断位,即将中断状态设置为false
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/**
* 实例方法 判断是否被中断过,返回线程的中断状态
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
}
上面的几个方法,可以写几个例子试一下,更加直观。
public class TestAqsInterrupt {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.setName("thread1");
MyThread thread2 = new MyThread();
thread2.setName("thread2");
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}
public static class MyThread extends Thread {
@Override
public void run() {
lock.lock();
try{
System.out.println(this.getName() + "获取到锁");
while (true) {
}
}finally {
lock.unlock();
}
}
}
}
上面代码没有任何逻辑,就是模拟两个线程争抢锁,获取到锁的线程会死循环,之后主线程睡眠2秒后中断中断未获得锁的线程,我们来看分析下未获得锁的线程接下来的执行情况。
thread1获取到锁进入死循环。thread2争抢锁失败加入到等待队列,并且调用了LockSupport.park(this);挂起。
此时主线程执行thread2.interrupt();
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; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
此时thread2挂起在parkAndCheckInterrupt方法中的LockSupport.park(this);处。
当调用thread2.interrupt();时,由于interrupt()方法内部会调用LockSupport.unpark(t),此时thread2会被唤醒继续执行,并且返回中断状态true(thread2.interrupt()方法设置)。回到acquireQueued方法中的15行,设置interrupted = true;
此时thread2继续执行acquireQueued中的for循环,由于thread1死循环正在占用锁,仍然获取不到锁继续挂起。所以我们是否设置了中断,其实对于thread2是没有影响的,只是会让他唤醒一次,**如果获取不到锁会继续排队。**这就是不检查中断的情况。
public class TestAqsInterrupt {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.setName("thread1");
MyThread thread2 = new MyThread();
thread2.setName("thread2");
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}
public static class MyThread extends Thread {
@Override
public void run() {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
try{
System.out.println(this.getName() + "获取到锁");
while (true) {
}
}finally {
lock.unlock();
}
}
}
}
相较于上一个例子,只是将加锁的方法由lock.lock();改为lock.lockInterruptibly();即检查中断。
thread1获取到锁进入死循环。thread2加入到等待队列,并且调用了LockSupport.park(this);挂起。
此时主线程执行thread2.interrupt();
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
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; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
此时thread2挂起在parkAndCheckInterrupt方法中的LockSupport.park(this);处。
当调用thread2.interrupt();时,由于interrupt()方法内部会调用LockSupport.unpark(t),此时thread2会被唤醒继续执行,并且返回中断状态true(thread2.interrupt()方法设置)。回到acquireQueued方法中的15行,设置interrupted = true;
thread2继续执行到doAcquireInterruptibly的16行,抛出中断异常,在抛出异常前调用finally中的cancelAcquire(node);方法(由于未获取到锁,failed为true),即取消排队。此时我们在外面catch到异常时就执行我们的异常处理就行。所以检查中断的结果就是如果线程被其他线程中断过,让线程取消排队,并且抛出中断异常。
如有不实,还望指正。