在AbstractQueuedSynchronizer内部,有一个队列,我们把它叫做同步等待队列。它的作用是保存等待在这个锁上的线程(由于lock()操作引起的等待)。
此外,为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer又需要再维护一个条件变量等待队列,也就是那些由Condition.await()引起阻塞的线程。由于一个重入锁可以生成多个条件变量对象,因此,一个重入锁就可能有多个条件变量等待队列。实际上,每个条件变量对象内部都维护了一个等待列表。
下面的类图展示了代码层面的具体实现:
无论是同步等待队列,还是条件变量等待队列,都使用同一个Node类作为链表的节点。
Node节点另外一个重要的成员是waitStatus,它表示节点等待在队列中的状态:
在具体的实现中,就可以简单的通过waitStatus是否小于等于0,来判断是否是CANCELLED状态。
获得请求许可的代码:
public final void acquire(int arg) {
//尝试获得许可, arg为许可的个数。对于重入锁来说,每次请求1个。
if (!tryAcquire(arg) &&
// 如果tryAcquire 失败,则先使用addWaiter()将当前线程加入同步等待队列
// 然后继续尝试获得锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 进入一步看一下tryAcquire()函数。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 该函数的作用是尝试获得一个许可。对于AbstractQueuedSynchronizer来说,这是一个未实现的抽象函数。
// 具体实现在子类中。在重入锁,读写锁,信号量等实现中,都有各自的实现。
如果tryAcquire()成功,则acquire()直接返回成功。如果失败,就用addWaiter()将当前线程加入同步等待队列。
// 接下来进入 addWaiter 逻辑,构造 Node 队列
private Node addWaiter(Node mode) {
// Node维护当前线程对象
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 将节点加入到队列尾端,这是一个快速方法compareAndSetTail,可能会失败
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果快速失败,就使用enq()函数,将node加入队列的尾部
enq(node);
return node;
}
接着, 对已经在队列中的线程请求锁,使用acquireQueued()函数,从函数名字上可以看到,其参数node,必须是一个已经在队列中等待的节点。它的功能就是为已经在队列中的Node请求一个许可。无论是普通的lock()方法,还是条件变量的await()都会使用这个方法.
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点的前驱结点,在下图所示中,其中第一个 Node 称为 Dummy(哑元)或哨兵,
// 用来占位,并不关联线程
// 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 请求失败,需要阻塞当前线程,由shouldParkAfterFailedAcquire()进行判断
// 简单的理解,对于前序节点是SIGNAL的,都需要park。对于已经CANCEL的节点,进行跳过删除。
// 对于初始节点和PROPAGATE节点,则设置为SIGNAL
if (shouldParkAfterFailedAcquire(p, node) &&
// park(),并且判断是不是等待过程中发生中断
parkAndCheckInterrupt())
// 如果发生中断,将中断信息保存
interrupted = true;
}
} finally {
if (failed)
// 如果意外失败了,比如异常之类的,那么久取消这个请求
cancelAcquire(node);
}
}
条件变量等待
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
// 设置Condition.await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 将节点加入到条件变量等待队列
Node node = addConditionWaiter();
// 进入等待队列之前,要释放自己所持有的的许可证,也可以叫做释放同步器上的锁(在lock里面)
// 同时唤醒同步等待队列第二个节点
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断当前节点是否在同步队列中
while (!isOnSyncQueue(node)) {
// 当前节点不再同步队列中,直接park挂起
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 到这里,表示我已经从等待中唤醒(有人signal我了),并且已经被放到同步等待队列中了
// 所以既然已经在同步队列中,就可以直接用acquireQueued()再次请求许可了
// 从await()中唤醒的线程,必须,一定要再次获得许可才行。
// 之前释放几个现在就要拿回来几个,不然lock()和unlock()数量就对不上号了。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程 创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
Condition对象的signal()通知
signal()通知的时候,是在条件等待队列中,按照FIFO进行,首先从第一个节点下手
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 从第一个节点开始
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// transferForSignal()把条件等待队列中的元素,移动到同步等待队列尾端
// 这样,当前面有许可可以使用时,它就可以自动被唤醒了,对于移动过程中,如果是一个已经CANCEL的节点,那么也会直接被唤醒
// transferForSignal()可能失败原因 节点被打断或者超时,放弃对许可证的获取
} while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
release()释放锁
public final boolean release(int arg) {
//tryRelease()是一个抽象方法,在子类中有具体实现和tryAcquire()一样
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 从队列中唤醒一个等待中的线程(遇到CANCEL的直接跳过)
unparkSuccessor(h);
return true;
}
return false;
}
AbstractQueuedSynchronizer 是一个比较复杂的实现,里面每一个方法和实现都值得慢慢推敲,要完全理解其中的细节还需要慢慢琢磨。