likes
comments
collection
share

AQS源码分析——一步一步带你走进AQS的世界(二)

作者站长头像
站长
· 阅读数 16

源码分析AQS

上一篇文章,以ReentrantLock的角度去分析了AQS的加锁和解锁,一行一行逐行分析,相信都能看懂,接下来我们要进入一个新的大关,就是Condition。

前文回顾

前面简单的介绍了一下阻塞队列,阻塞队列要记住的几个要点:

  1. 阻塞队列是一个双向链表
  2. 阻塞队列的头节点,head节点,不在阻塞队列中,head节点往往是拿到了锁的线程
  3. 阻塞队列里的节点被封装成了Node类

面试题

这里就分享一道本人面试阿里的时候出现的面试题:

为什么AQS唤醒线程的时候为什么从后向前遍历?这道题在上一篇文章已经解释过了,这里再提一下。

首先我们需要看看唤醒的这部分代码,如下所示:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    
	// 这里是查看当前节点的后序节点 s 如果s为null || s的状态失效 (状态值 > 0 可以理解为取消排队)
    // 这个时候就从尾部往前遍历 找到一个处于正常阻塞状态的节点进行唤醒
	Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒线程
}

可以看到在for循环中,是从后往前遍历,直到找到当前节点为止。

接下来我们来看节点的入队逻辑,为什么需要从后往前遍历,就是因为需要解决在高并发下入队可能会存在的问题,代码如下所示:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 初始化头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 注意这一步没有使用CAS 注意这一步没有使用CAS 注意这一步没有使用CAS
            node.prev = t;
            // 这一步使用 CAS 将当前节点替换为尾节点
            if (compareAndSetTail(t, node)) {
                //前驱节点的next指针指向当前节点
                t.next = node; 
                return t;
            }
        }
    }
}

可以看到在上述代码中,AQS使用了CAS来保证将当前节点置为尾节点的线程安全,但是在CAS之前的上一步,没有使用任何手段来保证线程安全,直接将当前节点的前序指针指向了尾节点

那么可以想象到,在高并发的情况下,会出现什么问题?

线程A通过CAS操作将当前节点置为了尾节点,这个时候线程A发生上下文切换,线程B再次使用CAS将当前节点置为尾节点,同时将这个if代码块中的内容全部执行完毕,那么这个情况下,整个流程如下图所示:

AQS源码分析——一步一步带你走进AQS的世界(二)

AQS源码分析——一步一步带你走进AQS的世界(二)

看到上面的两个图,就很清楚为什么需要需要从后往前遍历了。

Condition

首先Condition,翻译出来就是条件,所以这里要讲到AQS中的条件队列了,首先看看Condition的使用场景以及使用示例,Condition就是经典的被用来解决生产者/消费者问题的存在。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    // condition 依赖于 lock 来产生
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    // 生产
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                // 队列已满 等待 直到 not full 才能继续生产
                notFull.await();  
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            // 生产成功 队列已经 not empty 了 发个通知出去
            notEmpty.signal(); 
        } finally {
            lock.unlock();
        }
    }

    // 消费
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                // 队列为空 等待 直到队列 not empty 才能继续消费
                notEmpty.await(); 
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            // 消费数据 队列 not full 了 发个通知
            notFull.signal(); 
            return x;
        } finally {
            lock.unlock();
        }
    }
}

上面这个例子是Doug Lea给出的例子,很好的诠释了Condition的使用场景是什么,上面的例子很简单,下面来进行一个总结:

  1. 在使用Condition时,可以看到必须要依赖于锁lock去产生Condition,也就是说,一个锁lock,对应一个条件队列(条件队列是单向链表,不是双向链表,请牢记)在使用时必须要先持有相应的锁
  2. ArrayBlockingQueue采用的就是这样的方式实现了生产者消费者,实际生产中可以直接使用它,它内部持有ReentrantLock,相关源码如下所示:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /**
     * Serialization ID. This class relies on default serialization
     * even for the items array, which is default-serialized, even if
     * it is empty. Otherwise it could not be declared final, which is
     * necessary here.
     */
    private static final long serialVersionUID = -817911632652898426L;

    /** The queued items */
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    int count;

    final ReentrantLock lock;

    private final Condition notEmpty;

    private final Condition notFull;
    ......

首先,我们需要直到Condition与Reentrant和AQS的联系,需要捋清楚才能继续。

AQS源码分析——一步一步带你走进AQS的世界(二)

通过上图我们可以看出,AQS内部持有一个ConditionObject实现了Condition接口,而ReentrantLock是通过Sync来管理锁的,所以ReentrantLock通过Sync的newCondition方法去返回一个ConditionObject。

上面说过,使用Condition必须要获取相应的锁,而一个锁可以创建出多个Condition,这个看Sync的newCondition()方法就可以看出来了:

final ConditionObject newCondition() {
	return new ConditionObject();
}

接下来我们看AQS内部的ConditionObject类的源码,首先我们需要看看它的属性:

    public class ConditionObject implements Condition, java.io.Serializable {
        // 序列化用
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        // 头节点
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        // 尾节点
        private transient Node lastWaiter;
        ......

那么为什么说它是单向链表呢,主要原因在于Node类的源码中有一个属性为nextWaiter,如下所示:

    	// 前序指针——用于阻塞队列
		volatile Node prev;
        // 后序指针——用于阻塞队列
        volatile Node next;
        // 后序指针——用于条件队列
        Node nextWaiter;

在上一篇中,AQS有一个阻塞队列来保存等待获取锁的线程,这里引入另一个新的概念,就是条件队列,如下图所示:

AQS源码分析——一步一步带你走进AQS的世界(二)

上面这张图就能很好的诠释了Condition的处理流程了:

  • 阻塞队列中,线程被封装成Node,在条件队列中,线程依然是Node,因为条件队列的节点迟早是要转移到阻塞队列中去的。
  • 一个ReentrantLock可以创造多个条件队列,比如上图中的condition1、condition2。
  • 线程A调用condition1.await()方法,那么线程A会被封装成Node,加入condition1条件队列中,阻塞在这里,不继续往下执行,直到被唤醒。
  • 调用condition1.signal()方法,触发一次唤醒,唤醒的是对头,也就是firstWaiter节点,会将条件队列中的头节点转移到阻塞队列的队尾,等待获取锁,获取锁之后,才能在await方法返回,继续往下执行。

这里需要记住,阻塞队列和条件队列都是Node节点,并且条件队列的Node节点会被转移到阻塞队列中,等待获取锁,获取锁后才能在await方法处返回。

接下来走代码分析:

	// 看到抛出中断异常就应该意识到这个方法是可以中断的
	public final void await() throws InterruptedException {
            // 判断中断状态
            if (Thread.interrupted())
                throw new InterruptedException();
        	// 将当前线程封装成Node节点加入到Condition的条件队列中
            Node node = addConditionWaiter();
        	// 这一步就是释放锁 并且记录释放锁之前Node的状态值
            // 因为如果它后续加入了阻塞队列并且拿到了锁 肯定是需要恢复到之前拿到锁的状态
        	// 比如 可重入锁 重入了2次 那么恢复它的时候 就应该拿到2次锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
        	// 看到循环 应该分析推出循环的条件了
        	// 1 当isOnSyncQueue(node) 返回true的时候 也就是 node已经被转移到阻塞队列了
        	// 这里的设计十分巧妙 到下面我们看到源码就知道了
            // 2 当发生线程中断的时候 会从循环break出来
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
        	// 能运行到这里 要么被唤醒 要么被中断
        	// 唤醒之后的流程就是 加入到阻塞队列中 竞争锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

接下来我们来详细分析其中的方法。

addConditionWaiter()方法——将节点加入到条件队列中

	private Node addConditionWaiter() {
            // t 记录尾节点
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            // 如果尾节点的状态值是CONDITION(-2) 就将其清除出去
            // 注意这里只要状态值 != -2就被认定为是取消排队 原因在下面就可以看到
            if (t != null && t.waitStatus != Node.CONDITION) {
                // 遍历整个条件队列 将取消排队的节点全部都清除出去
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 将当前线程包装成Node节点
            // 这里需要注意 在初始化的时候就已经将节点的状态值置为了CONDITION
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            // 如果尾节点为空 则直接将node放在头节点的位置上
            if (t == null)
                firstWaiter = node;
            else
                // 否则就让尾节点的后序指针指向node 这是node就成为了最新的尾节点
                t.nextWaiter = node;
            // 将尾节点指针更新为node
            lastWaiter = node;
            return node;
        }

上面的代码就是将当前线程封装成Node,再把Node加入到条件队列中的队尾。

接下来分析unlinkCancelledWaiters()方法,这个方法就是遍历整个条件队列,清除条件队列中取消等待的节点,这个方法被调用的时机就是:

  1. 在await方法时,如果发生了取消操作
  2. 在节点入队的时候,发现尾节点的状态是被取消的

都会调用这个方法。

	private void unlinkCancelledWaiters() {
            // 记录头节点
            Node t = firstWaiter;
            Node trail = null;
            // 向后遍历
            while (t != null) {
                Node next = t.nextWaiter;
                // 只要节点的状态值不是CONDITION就被认为是取消排队的
                if (t.waitStatus != Node.CONDITION) {
                    // 链表操作 将不符合条件的链表剔除出去
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

fullyRelease(node)方法——完全释放独占锁

回到await方法,上一步是将节点加入到了条件队列中,下一步就是完全释放锁,并且记录锁的state值,这一部分上面已经解释过了,因为ReentrantLock是可重入的,所以在下次在被唤醒的时候,应该要恢复到之前的状态才可以继续,比如它之前重入了2次锁,那么state值就应该为2,那么唤醒它的时候,就也需要获取两次锁才能继续执行。

	// 这个方法如果失败 会将节点设置为取消状态 并且抛出IllegalMonitorStateException异常
	final int fullyRelease(Node node) {
        // 成功 / 失败 标识
        boolean failed = true;
        try {
            // 记录当前的state值
            int savedState = getState();
            // 这里就是完全释放锁 将state置为0
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

看到这里,就能知道为什么一定要先持有锁,才能调用await方法,如果一个线程A没有持有锁就直接调用了await方法,那么在这个方法中的release(savedState)处一定会抛出异常,然后将这个节点的状态值设置为取消排队,这样就会被后续的节点清除掉。

等待进入阻塞队列

        	int interruptMode = 0;
        	// 循环调用挂起方法 防止被虚假唤醒
            while (!isOnSyncQueue(node)) {
                // 线程挂起
                LockSupport.park(this);
                // 判断线程是否被中断了
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

isOnSyncQueue()方法用于判断当前节点是否已经被转移到阻塞队列中了:

	final boolean isOnSyncQueue(Node node) {
        // 这里就是判断节点的状态值是否是等待状态 | 节点的prev指向是否是null
        // 这里的prev这里需要这么去理解 prev是阻塞队列中使用的 如果它的prev指向的是null 说明肯定没有在阻塞队列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
    	// 如果node有后序节点 next的时候一定是在阻塞队列中的
        if (node.next != null) // If has successor, it must be on queue
            return true;
        // 这个方法就是从阻塞队列的队尾开始寻找 从后往前遍历 如果找到相等的说明在阻塞队列中
    	// 如果没有找到 说明还在条件队列中
        return findNodeFromTail(node);
    }
	// 从阻塞队列的队尾 往前遍历 如果找到 则返回true
	private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

回到前面的循环中,如果isOnSyncQueue方法返回false,则需要调用LockSupport.park(this)方法将线程挂起。

signal唤醒线程,将Node转移到阻塞队列中

// 唤醒条件队列中的头节点——也就是等待了最久的节点(当然也有可能唤醒失败 因为可能头节点取消等待)
// 将这个线程对应的 node 从条件队列转移到阻塞队列
public final void signal() {
    // 调用 signal 方法的线程必须持有当前的独占锁 否则抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

// 从条件队列队头往后遍历,找出第一个需要转移的 node
// 因为前面我们说过 有些线程会取消排队 但是可能还在队列中
private void doSignal(Node first) {
    do {
        // 将firstWaiter指针指向first的下一个节点
        // 如果firstWaiter 为 null 的话就说明条件队列为空了 将尾节点置为null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        // 因为 first 要被移到阻塞队列了 和条件队列的链接关系在这里断掉
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
      // 这里 while 循环 如果 first 转移不成功(节点取消排队) 那么选择 first 后面的第一个节点进行转移 依此类推
}

// 将节点从条件队列转移到阻塞队列
// true 代表成功转移
// false 代表在 signal 之前 节点已经取消了
final boolean transferForSignal(Node node) {

    // CAS 如果失败 说明此 node 的 waitStatus 已不是 Node.CONDITION 说明节点已经取消 
    // 既然已经取消 也就不需要转移了 方法返回 转移后面一个节点
    // 否则 将 waitStatus 置为 0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    // enq(node): 自旋进入阻塞队列的队尾
    // 注意 这里的返回值 p 是 node 在阻塞队列的前驱节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁 直接唤醒 node 对应的线程
    // 唤醒node节点之后会发生什么 后面会说明
    // 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用 上篇介绍的时候说过
    // 节点入队后 需要把前驱节点的状态设为 Node.SIGNAL(-1) 表明当前节点的后序节点需要被唤醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果前驱节点取消排队 或者 CAS 失败 会进到这里唤醒线程 之后的操作看下一节
        LockSupport.unpark(node.thread);
    return true;
}

正常情况下ws<=0并且CAS操作会返回true,所以一般不会进入if语句唤醒节点,然后返回true,表明这个节点已经成功加入阻塞队列中。假如出现了特殊情况,阻塞队列中的前序节点的状态值大于0(ws > 0)或者CAS失败,就需要唤醒线程,让其进行到下一步。

唤醒后检查中断状态

在上一步signal唤醒线程之后,将Node转移到阻塞队列后就等待获取锁,只要重新获取到了锁就可以继续往下执行,就走到了如下代码所示:

int interruptMode = 0;
while (!isOnSyncQueue(node)) {
    // 线程挂起 -- 从这里醒来
    LockSupport.park(this);
	// 检查中断状态
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}

首先我们需要看到interruptMode变量,这个变量可以取的值有0、THROW_IE(-1)、REINTERRUPT(1),这个取值可以在await方法中可以看到有这样的三个取值。

  • 0:初始化的时候的值,说明在await期间没有发生中断
  • THROW_IE(-1):代表 await 返回的时候,需要抛出 InterruptedException 异常
  • REINTERRUPT(1):代表 await 返回的时候,需要重新设置中断状态

有四种情况会LockSupport.park(this)这句话继续往下执行。

  1. signal后转移节点到阻塞队列中并且成功获取锁
  2. 线程中断,在park的时候,另一个线程对这个线程进行了中断
  3. signal的时候,转移节点的前序节点取消了,或者改变前序节点状态值的CAS操作失败了
  4. 虚假假唤醒,Object.wait()也会存在这样的问题,所以一般都是在循环挂起线程

线程被唤醒之后,第一步就是检查线程是否在挂起期间发生中断,如果发生中断,需要判断是在signal之前发生的中断还是在signal之后发生的中断,然后赋值给interruptMode。

  • 在signal之前发生的中断,返回THROW_IE (-1)
  • 在signal之后发生的中断,返回REINTERRUPT (1)
  • 没有发生中断,返回0
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

Thread.interrupted():如果线程已经发生中断了,返回true并且将中断状态重置为false,所以才有后序的重新中断(REINTERRUPT)的使用。

看看是如何判断是在signal之前发生的中断还是在signal之后发生的中断:

// 能进入到这个方法 一定是线程处于中断状态 否则直接返回0 不会进入到该方法
final boolean transferAfterCancelledWait(Node node) {
    // 用 CAS 将节点状态设置为 0 
    // 如果这步 CAS 成功 说明是 signal 方法之前发生的中断 
    // 因为如果 signal 先发生的话 signal过程中会将 waitStatus 设置为 0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        // 将节点放入阻塞队列 记住 即使中断发生了 依然会放入阻塞队列中
        enq(node);
        return true;
    }

    // 到这里是因为 CAS 失败 肯定是因为 signal 方法已经将 waitStatus 设置为了 0
	// signal 方法将节点转移到阻塞队列 但是可能没有完成转移 下面就是在等待它 完成转移
    // 这种情况就是:signal 调用之后 没完成转移之前 发生了中断
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

这里需要记住,即使发生了中断,还是会将节点转移到阻塞队列中去,所以上述方法中的while循环,要么节点完成转移返回,要么发生中断返回。

这里说的就是,假如线程A在条件队列中,此时它被中断了,中断了就会被唤醒,它被唤醒之后发现自己不是被signal的那个线程,此时它依然会主动进入阻塞队列中去。

获取独占锁

在await方法中的循环退出之后,进入的就是如下代码中:

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;

退出while循环,就说明节点一定进入了阻塞队列中,准备获取锁,所以这里就是将线程还原回释放锁之前的状态,也就是将state值变为之前的saveState值,acquireQueued(node, savedState)这个方法返回的时候,就是代表线程获取了锁,并且state=saveState。

注意,不管是否发生中断,节点都会进入到阻塞队列中,acquireQueued(node, savedState) 的返回值就是代表线程是否被中断。如果返回true,表示发生中断,此时再判断interruptMode != THROW_IE,说明在signal之前就已经发生中断了,那么就需要将interruptMode值置为REINTERRUPT,用于待会儿重新中断。

继续往下走代码:

if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();
if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);

这里说说node.nextWaiter != null怎么满足,在signal的时候会将节点转移到阻塞队列中,并且有一步就是node.nextWaiter = null,将节点和条件队列的联系断开,但是如果在signal之前发生中断,此时节点也会进入阻塞队列中,并且此时没有将节点与条件队列的联系断开,也就是没有执行node.nextWaiter = null这一步。

如果node.nextWaiter != null,那么就会清除条件队列中取消排队的节点,全部清除出去,这个方法在上面已经讲过了,接下来就会进入到reportInterruptAfterWait方法,处理中断状态。

处理中断状态

这里就又要提到interruptMode这个值了,稍微回顾一下:

  • 0:表示没有被中断过
  • THROW_IE:await方法抛出中断异常,代表在await期间发生了中断
  • REINTERRUPT:重新中断线程,它表示await期间没有发生中断,而是在signal之后发生的中断
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    // 如果interruptMode为THROW_IE,表示在await期间发生中断,直接抛出中断异常
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    // 如果interruptMode为REINTERRUPT,表示线程需要再次中断
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}
static void selfInterrupt() {
    // 当前线程中断
	Thread.currentThread().interrupt();
}

带超时机制的await方法

带超时机制的await很简单,之前是调用park方法来挂起线程,等待被唤醒,而现在是调用parkNanos方法,来指定休眠的时间,醒来之后再判断signal是否被调用,调用了就是没有超时,否则就是超时了,超时的话,自己来进行转移到阻塞队列中,然后进行正常流程的抢锁。

AQS独占锁的取消排队

接下来就应该说说AQS如何取消排队,我们需要在acquireQueued这个方法中找答案:

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

这个方法在上一篇文章中已经是介绍过了,能进入到这个方法的一定是节点已经存在于阻塞队列中的,下面看到parkAndCheckInterrupt方法:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

把这两段代码连起来看,会稍微清楚一点,首先,如果我们需要取消一个线程的排队,就需要另一个线程去中断它,比如线程A调用lock方法后很久不返回,此时线程B中断线程A,线程A在LockSupply.park(this)方法中被唤醒,然后执行Thread.interrupted();返回true。

这里需要注意,中断线程不等于结束运行,这里只是简单的记录了一下它发生中断过,然后继续下一次循环抢锁,并且由于Thread.interrupted()方法会清除中断标志,下一次进来就是返回false。并且这里方法的出口就只有一个,就是返回interrupted值,如果发生中断,这个值就会变成true,然后在抢到锁时返回true。

这个时候需要联系acquireQueued的外层方法,来看看外层方法是怎么处理acquireQueued方法返回true的。(返回true,说明线程发生中断)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

这里看到很简单,acquireQueued方法返回true,那么就会设置线程中断状态,但是不抛出任何异常。

这里的逻辑就是,lock方法处理中断,即便是发生了中断也没关系,我只是记录一下发生过中断,我还是会进行抢锁,抢到锁之后,就设置一下线程的中断状态,但是也不抛出任何异常,调用者在获取锁之后,可以去判断是否发生过中断,也可以不去管它

目前和取消排队还没有什么联系,但是接下来可以看到另一个的lock方法:

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

方法上多了一个抛出中断异常的方法,继续看里面的代码:

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

继续往里看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; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 可以看到这里和上面的lock方法不一样
                // 这里不再是标记中断标识 然后继续下一次循环了 而是直接抛出异常
                // 一直往外抛出
                throw new InterruptedException();
        }
    } finally {
        // 如果通过 InterruptedException 异常出去,那么 failed 就是 true 了
        if (failed)
            cancelAcquire(node);
    }
}

这里需要注意的就是要理解一点,中断不等于退出程序,中断在线程中只是一个中断标识,是一个true or false,初始值为false,后面会写一篇关于中断的详细解释。