今天我们分享并发编程 AQS 思想之AbstractQueuedSynchronizer类的源码解析:
![](https://img-blog.csdnimg.cn/2020121416221094.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25hbmRhbzE1OA==,size_16,color_FFFFFF,t_70)
4、源码参数含义如下:
static final class Node {
/**
* 表示线程以共享的模式等待锁(如 ReadLock)
* */
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
/**
* 表示线程以互斥的模式等待锁(如 ReetrantLock),互斥就是一 把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁
* */
static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;
/**
*值为 1,表示线程的获锁请求已经“取消”
* */
static final int CANCELLED = 1;
/**
* 值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
* */
static final int SIGNAL = -1;
/**
*值为-2,表示线程等待某一个条件(Condition)被满足
* */
static final int CONDITION = -2;
/**
* 值为-3,当线程处在“SHARED”模式时,该字段才会被使用 上
*/
static final int PROPAGATE = -3;
/**
*该 int 变量表示线程在队列中的状态,其值就是上述提到的 CANCELLED、SIGNAL、CONDITION、PROPAGATE
*/
volatile int waitStatus;
/**
*该变量类型为 Node 对象,表示该节点的前一个 Node 节点(前驱)
*/
volatile AbstractQueuedSynchronizer.Node prev;
/**
* 该变量类型为 Node 对象,表示该节点的后一个 Node 节点(后继)
*/
volatile AbstractQueuedSynchronizer.Node next;
/**
* 该变量类型为 Thread 对象,表示该节点的代表的线程
*/
volatile Thread thread;
/**
* 该变量类型为 Node 对象,表示等待 condition 条件的 Node 节 点
*/
AbstractQueuedSynchronizer.Node nextWaiter;
/**
* 如果节点在共享模式中等待,则返回true.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回前一个节点,如果为空则抛出NullPointerException。
* @return the predecessor of this node
*/
final AbstractQueuedSynchronizer.Node predecessor() throws NullPointerException {
AbstractQueuedSynchronizer.Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, AbstractQueuedSynchronizer.Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
5、 即其中包括了:
![](https://img-blog.csdnimg.cn/20201215222613260.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25hbmRhbzE1OA==,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20201215222621541.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25hbmRhbzE1OA==,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20201215222639930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25hbmRhbzE1OA==,size_16,color_FFFFFF,t_70)
public final void acquire(int arg) {
/**
* !tryAcquire(arg) 返回false,抢到锁,直接短路,不往下走;返回true未抢到锁,往下走。
**/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先调用自定义同步器实现的 tryAcquire(int arg)方法,该方法需要保证线程 安全的获取同步状态。 如果同步状态获取失败(tryAcquire 返回 false),则构造同步节点(独占式 Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过 addWaiter(Node node)方法将该节点加入到同步队列的尾部, 最后调用 acquireQueued(Node node,int arg)方法,使得该节点以“死循环” 的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒 主要依靠前驱节点的出队或阻塞线程被中断来实现。 addWaiter(Node node)方法中
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;
}
将当前线程包装成 Node 后,队列不为空的情况下,先尝试把当前节点加入 队列并成为尾节点,如果不成功或者队列为空进入 enq(final Node node)方法。
private Node enq(final Node node) {
for (;;) {//死循环,自旋
Node t = tail;
if (t == null) { //首次 创建新的节点队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//设置尾节点
t.next = node;
return t;
}
}
}
}
在 enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添 加,这个死循环中,做了两件事,第一件,如果队列为空,初始化队列,new 出 一个空节点,并让首节点(head)和尾节点(tail)两个引用都指向这个空节点; 第二件事,把当前节点加入队列。 在“死循环”中只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从 该方法返回,否则,当前线程不断地尝试设置。 节点进入同步队列之后,观察 acquireQueued(Node node,int arg)方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//此处自旋不会太耗费CPU,因为首次失败后会进入阻塞状态
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);
}
}
![](https://img-blog.csdnimg.cn/20201214193007157.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25hbmRhbzE1OA==,size_16,color_FFFFFF,t_70)
public final boolean release(int arg) {
if (tryRelease(arg)) {//释放锁,子类实现
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒首节点(head)所指向节点的后继节点线程
return true;
}
return false;
}
该方法执行时,会唤醒首节点(head)所指向节点的后继节点线程, unparkSuccessor(Node node)方法使用 LockSupport 来唤醒处于等待状态的线程。 而在 unparkSuccessor 中,
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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);//唤head 指向节点的后继节点线程
}
这段代码的意思,一般情况下,被唤醒的是 head 指向节点的后继节点线程, 如果这个后继节点处于被 cancel 状态,(我推测开发者的思路这样的:后继节点 处于被 cancel 状态,意味着当锁竞争激烈时,队列的第一个节点等了很久(一直 被还未加入队列的节点抢走锁),包括后续的节点 cancel 的几率都比较大,所以) 先从尾开始遍历,找到最前面且没有被 cancel 的节点。