AbstractQueuedSynchronizer(AQS)提供了一套可用于实现锁同步机制的框架,不夸张地说,AQS是JUC同步框架的基石。AQS通过一个FIFO队列维护线程同步状态,实现类只需要继承该类,并重写指定方法即可实现一套线程同步机制。
AQS根据资源互斥级别提供了独占和共享两种资源访问模式;同时其定义Condition结构提供了wait/signal等待唤醒机制。在JUC中,诸如ReentrantLock、CountDownLatch等都基于AQS实现。 ASQ中存在两个对象,对象一是AQS实例对象,对象二是实例对象内存储的阻塞队列(头节点和尾节点);
- 初始化阻塞队列: 头节点为无线程的空节点(
new Node()),尾节点指向头节点。 - 第一个加入阻塞队列的节点(
new Node(Thread)),形成head<->node...
本文主要讲解AQS中,线程遇到阻塞情况,加入到线程等待队列的原理,下面是线程获取独占锁的部分源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
本次不讲解tryAcquire尝试获取锁的代码,只讨论当线程获取锁失败,尝试加入等待队列的情景。
addWaiter函数负责将新增目标节点添加到当前阻塞队列的队尾,下面是源码:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
上述源码中tail指的是锁对象持有链表的尾节点,pred则真正代表了链表的尾节点,并参与了增链。
Node node = new Node(Thread.currentThread(), mode);负责根据当前线程创建一个节点,该节点将会被添加到队列末尾。Node pred = tail;将当前锁对象存储的尾节点赋值给pred。if (pred != null)若队列末尾节点为空,则认为队列是空的,通过enq进行队列初始化。node.prev = pred;将目标节点node挂载在尾节点pred的尾部,node->pred;此处保证了逆向链表的准确性,此时会出现多个目标node同时指向pred。if (compareAndSetTail(pred, node)),该函数通过CAS判断,若当前线程获取的pred与锁对象中tail位置存储的尾节点是否一致,一致的话将锁对象的tail未知换成node(其他线程此时手持的是上一个尾节点pred,与现在的尾节点node自然不一致)。pred.next = node;pred是链表的尾节点,指向node,形成双链表pred<->node。return node;并将node节点返回,node则是当前链表的尾节点。
在队列是空的情况下,enq函数负责初始化等待队列,并将目标节点添加其中。以下是enq的源码:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
该函数内置一个无限循环,当尾节点是空的时候(t == null),通过CAS在head位置放置一个空节点(new Node(),无线程不会参与竞争锁),同时将尾节点设置成头节点; 此初始化过程意味着队列中有一个个空节点,不是实质存储线程参与竞争的节点;此时阻塞队列已经形成。