主要参考了博客JUC框架 源码解析系列文章目录 JDK8
AbstractQueuedSynchronizer
概述
实现大量依赖乐观锁的方式(即CAS+自旋)。它实现了一个FIFO的等待队列用于等待获取同步状态,而获取/释放同步器状态的函数则依靠子类来实现。
虽然AQS是一个抽象类,但却没有任何抽象方法。如果定义为抽象方法确实不合适,因为继承使用AQS并不一定需要使用到AQS提供的所有功能(独占锁和共享锁),这样子类反而需要实现所有抽象方法。如果定义为空实现的普通方法,虽然不需要子类实现所有空方法了,但这样还是不够明确。现在AQS将这些方法的实现为抛出UnsupportedOperationException异常,那么如果是子类需要使用的方法,就覆盖掉它;如果是子类不需要使用的方法,一旦调用就会抛出异常。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
简单使用案例(实现一个共享锁):
自定义同步器实现时主要实现以下几种方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
public class MyLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) throw new IllegalArgumentException();
setState(count);
}
@Override
protected int tryAcquireShared(int acquireCount) {
while (true) {
int cur = getState();
int newCount = cur - acquireCount;
if (newCount < 0 || compareAndSetState(cur, newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int releaseCount) {
while (true) {
int cur = getState();
int newCount = cur + releaseCount;
if (compareAndSetState(cur, newCount)) {
return true;
}
}
}
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
}
重要属性
state
- state用volatile修饰,保证了它的可见性。
- 如果是多线程并发修改的话,采用compareAndSetState来操作state
- 如果是在没有线程安全的环境下对state操作(例如ReentrantLock释放锁,因为它之前已经获取到独占锁,所以没必要用CAS),采用setState方法
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
等待队列
AQS中已经为我们实现了一个FIFO的等待队列,它是一个双向链表。由于同步器的state一般不能让所有线程同时获得,所以将这些需要暂时等待的线程包装成一个节点放到队列中去,当获取state的条件满足时,会将这个节点内的线程唤醒,以便它接下来去尝试获取state。
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus; // 表明node代表线程的状态
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter; // 表明当前node的线程是想要获取共享锁还是独占锁
final boolean isShared() {
return nextWaiter == SHARED;
}
}
private transient volatile Node head; // 固定是一个dummy node,因为它的thread成员固定为null
private transient volatile Node tail; // 请求锁失败的线程,会包装成node,放到队尾
- head节点中thread成员为null,可以理解为将它的thread成员放到AQS的
exclusiveOwnerThread
属性上
- 即使等待线程只有一个,等待队列中的节点个数也肯定是2个,因为第一个节点总是dummy node。
acquire(int arg)
流程
- 首先调用子类的
tryAcquire
尝试获取独占锁一次,try的意思就是只试一次,要么成功,要么失败。 - 获取不到则调用
addWaiter(Node.EXCLUSIVE)
将该线程加入等待队列的尾部,并标记为独占模式 -
acquireQueued
使线程在等待队列中获取资源,中途可能不断经历阻塞/唤醒状态,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。 - 如果线程在等待过程中被中断过,它是不响应的。但是当
acquireQueued
返回真时,代表这期间函数曾经检测到过中断状态,并且将中断状态消耗掉了(Thread.interrupted()
),所以需要在退出acquire
之前,将中断状态重新设置上。
/**
Acquires in exclusive mode, ignoring interrupts.
Implemented by invoking at least once tryAcquire, returning on success.
Otherwise the thread is queued,
possibly repeatedly blocking and unblocking, invoking tryAcquire until success.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
该方法的默认实现是抛出UnsupportedOperationException,具体实现由自定义的扩展了AQS的同步类来实现。
AQS在这里只负责定义了一个公共的方法框架。
没有定义成abstract,是因为独占模式下只用实现tryAcquire-tryRelease,
而共享模式下只用实现tryAcquireShared-tryReleaseShared。
如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
addWaiter(Node)
- 将当前线程封装成一个节点(
Node.EXCLUSIVE
互斥模式、Node.SHARED
共享模式) - 尝试快速入队:通过一次CAS加入到等待队列的队尾。
- 如果CAS失败或者队列为空,则通过enq(node)方法初始化一个等待队列
- 在enq(node)中,如果队列为空,则会给头部设置一个空节点:
compareAndSetHead(new Node())
,随后不断自旋直到把node加入到等待队列队尾。这个循环只有在compareAndSetTail(t, node)
成功时才会退出循环,这就保证了enq最终肯定能将参数node放到队尾。就算只有一个线程入队,入队完毕后队列将有两个node,第一个node称为dummy node,因为它的thread成员为null;第二个node才算是实际意义的队头,它的thread成员不为null。新建的是空node,它的所有成员都是默认值。thread成员为null,waitStatus为0。之后你会发现,队尾node的waitStatus总是0,因为默认初始化。 - 返回当前线程所在的结点
注意点
如果是多线程执行,可能导致多个node.prev链接到了tail,但是通过CAS保证tail.next只会链接到其中一个Node,并且其他的Node在不断的自旋中最终还是会加入到等待队列中
prev的有效性:有可能产生这样一种中间状态,即node.prev指向了原先的tail,但是tail.next还没来得及指向node。这时如果另一个线程通过next指针遍历队列,就会漏掉最后一个node。但是如果是通过tail.prev来遍历等待队列,就不会漏掉节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return 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;
}
}
}
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
acquireQueued
- 每次循环都会判断是否可以尝试获取锁(判断前驱节点p是否为head),如果可以,那么尝试tryAcquire(arg)
- 如果不可以尝试,或者获取锁失败,则通过parkAndCheckInterrupt阻塞线程并检查线程中断状态
- 如果线程被unpark/interrupt,则会从park中返回,接着从parkAndCheckInterrupt()返回,继续往下执行
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;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node); // 该方法不会被执行
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
shouldParkAfterFailedAcquire
Node的状态
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
CANCELLED代表线程已经取消等待
SIGNAL说明这个node的后继node的代表线程已经阻塞或马上阻塞。当前node成为head并释放锁时,会根据SIGNAL来唤醒后继node。即SIGNAL是唤醒后继节点的标志。
- 一个node新建的时候,它的waitStatus是默认初始化为0
说明
获取锁失败了才会执行该函数:
- p == head为false,即当前线程的node的前驱不是head
- 虽然 p == head为true,虽然当前线程虽然已经排到等待队列的最前面,但获取锁还是失败了
只有当该函数返回true时,才会去执行parkAndCheckInterrupt
作用:跳过无效前驱,把node的有效前驱(有效是指node不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL,之后便返回true代表马上可以阻塞了。给前一个节点设置SIGNAL,相当于一个闹钟,当前一个节点释放锁时,唤醒当前节点
执行两次
如果刚开始前驱的状态为0,那么需要第一次执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL)返回false并进入下一次循环,第二次才能进入if (ws == Node.SIGNAL)分支,所以说至少执行两次。死循环保证了最终一定能设置前驱为SIGNAL成功的。(考虑当前线程一直不能获取到锁)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) return true;
if (ws > 0) {
do {
// 是CANCELLED,说明前驱节点已经因为超时或响应了中断,而取消了自己,需要跳过他们
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 把一个node的状态变成SIGNAL
}
return false;
}
parkAndCheckInterrupt
调用完LockSupport.park(this),当前线程就阻塞在这里,直到有别的线程unpark了当前线程,或者中断了当前线程。而返回的Thread.interrupted()代表当前线程在阻塞的过程中,有没有被别的线程中断过,如果有,则返回true。注意,Thread.interrupted()会消耗掉中断的状态,即第一次执行能返回true,但紧接着第二次执行就只会返回false了。
如果是别的线程unpark了当前线程,那么调用Thread.interrupted()返回false。
如果是别的线程中断了当前线程,那么调用Thread.interrupted()返回true。
回到acquireQueued的逻辑中,发现一旦当前线程被中断过一次,那么parkAndCheckInterrupt
就返回了true,那么执行interrupted = true
,interrupted
局部变量就一定是true的了。(该中断状态会永久保留,用于最外层acquire中恢复用户中断)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
注意点
忽略中断
整个过程忽略用户发出的中断信号(也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出),直到acquireQueued执行结束后,才通过selfInterrupt恢复用户的中断
为什么tryAcquire(arg)的前提是p == head?
从enq的逻辑可知,head只会是一个dummy node,实际意义的node只会在head的后面。而node的前驱是head(final Node p = node.predecessor()),则代表node已经是队列中的第一个实际node了,排在最前面的node自然可以尝试去获取锁了。
回想整个调用过程,是最开始在acquire里调用tryAcquire就已经失败了,然而此时第一次循环时,又可能马上去调tryAcquire(说可能,是因为需要p == head成立),这会不会是一次肯定失败的tryAcquire?
考虑这种场景,线程1获取了锁正在使用还没释放,此时队列为空,线程2此时也来获取锁,自然最开始在acquire里调用tryAcquire会失败,假设线程2刚开始执行acquireQueued,此时线程1释放了锁,此时线程2肯定排在head后面,那么线程2马上tryAcquire,然后就可以获取成功。
执行acquireQueued的线程是谁?
一定是node参数的thread成员,虽然执行过程中,可能会经历不断阻塞和被唤醒的过程。
为什么刚执行完addWaiter方法时,才把代表当前线程的node放到队尾,怎么之后一判断就会发现自己处于head的后继了?
考虑addWaiter时,队列中有许多node。这说明从head到当前方法栈中的node之间的那些node,它们自己也会在执行acquireQueued,它们依次执行成功(指p == head && tryAcquire(arg)成功),每次执行成功相当于把head成员从队列上后移一个node,当它们都执行完毕,当前方法栈中的node自然也就是head的后继了。
“之间的那些node”的最后一个node执行acquireQueued成功后(代表 最后一个node的代表线程获得锁成功,它自己成为了head),当前方法还在阻塞之中,只有当这“最后一个node”释放独占锁时,才会执行unparkSuccessor(head),当前线程才会被唤醒。
finally块是否会执行cancelAcquire(node)?
虽然号称此函数是不响应中断的函数,但不响应中断只是对于AQS的使用者来说,如果一个线程阻塞在parkAndCheckInterrupt这里,别的线程来中断它,它是会马上唤醒的,然后继续这个循环。不过想要退出这个函数,只有通过return interrupted,而前一句就是failed = false,所以finally块里,是永远不会去执行cancelAcquire(node)的。
release(int arg)
独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。
释放锁的过程,根本不会区分公平或不公平、响应中断或不响应中断、超时或不超时。这是因为,这些区别都只是存在于尝试获取锁的方式上而已,既然已经获得了锁,也就不需要有这些区别。
细节
- 如果遇到
s == null
,说明我们遇到一种中间状态,next指针还没有指好。如果遇到s.waitStatus > 0
,说明head后继刚取消了。这两种情况,都需要从队尾的prev往前找。 - 注意循环条件
t != null && t != node
,它会从队尾一直往前找,直到t
是null或t
已经到达了node
。一般情况下,不会出现t != null
,所以,这样循环肯定能找到node
之后第一个不是取消状态的节点。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 如果从头到尾都只有一个线程在使用锁,那么队列也不会初始化,head肯定为null。
// 当队列只有一个dummy node时,它的状态为0,也就不会执行unparkSuccessor(h)了
// 当head的状态为SIGNAL时,说明head后继已经设置了闹钟,会执行unparkSuccessor(h)。
if (h != null && h.waitStatus != 0) unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
/*
* head后继一般能直接通过next找到,但只有prev是肯定有效的。
* 所以遇到next为null,肯定需要从队尾的prev往前找。
* 遇到next的状态为取消,也需要从队尾的prev往前找。
*/
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);
}
acquireInterruptibly(int arg)
进入这个方法后,会第一次进行tryAcquire
尝试。但不同的,此acquireInterruptibly
函数中,会去检测Thread.interrupted()
,并抛出异常。
对于acquireInterruptibly
这个方法而言,既可以是公平的,也可以是不公平的,这完全取决于tryAcquire
的实现(即取决于ReentrantLock当初是怎么构造的)。
如果检测到中断信号,首先线程会从LockSupport.park()中返回,并且抛出InterruptedException异常,执行cancelAcquire方法,将该线程代表的节点从等待队列中移除,并根据情况选择是否unparkSuccessor后续节点
doAcquireInterruptibly
不需要返回值,因为该函数中如果检测到了中断状态,就直接抛出异常就好了。
doAcquireInterruptibly
方法的finally块是可能会执行到cancelAcquire(node)
的,而acquireQueued
方法不可能去执行cancelAcquire(node)
的。在doAcquireInterruptibly方法中,如果线程阻塞在parkAndCheckInterrupt这里后,别的线程来中断阻塞线程,阻塞线程会被唤醒,然后抛出异常。本来抛出异常该函数就马上结束掉的,但由于有finally块,所以在结束掉之前会去执行finally块,并且由于failed为true,则会执行cancelAcquire(node)。
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 void cancelAcquire(Node node) {
if (node == null) return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 跳过CANCELLED的节点
// 执行完循环,pred会指向node的有效前驱
Node predNext = pred.next;
// 如果别的线程在执行这步之后,别的线程将会跳过这个node。
// 如果别的线程在执行这步之前,别的线程还是会将这个node当作有效节点。
node.waitStatus = Node.CANCELLED;
// 如果node是队尾,直接设置pred为队尾,然后设置pred的后继为null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
tryAcquireNanos(int arg, long nanosTimeout)
tryAcquireNanos这个方法与不响应中断的acquire方法对应。同样的,进入这个方法后,会第一次进行tryAcquire尝试。但不同的,此tryAcquireNanos函数中,会先去检测Thread.interrupted(),并抛出异常。
但注意,对于tryAcquireNanos这个方法而言,既可以是公平的,也可以是不公平的,这完全取决于tryAcquire的实现(即取决于ReentrantLock当初是怎么构造的)。
差别
每次循环都会检查时间是否到达deadline。
当剩余时间小于spinForTimeoutThreshold时,则不能调用LockSupport.parkNanos,因为时间太短,反而无法精确控制阻塞时间,所以不如在剩余的时间里一直循环。
LockSupport.parkNanos除了会因为别人的park而唤醒,也会因为别人的中断而唤醒,当然最重要的,时间到了,它自己会唤醒。
不管哪种情况,被唤醒后,都会检查中断状态。每个循环都会检查一次。
如果中断,也同样进入cancelAcquire方法
final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L) return false;
final long deadline = System.nanoTime() + nanosTimeout;
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 true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L) return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted()) throw new InterruptedException();
}
} finally {
if (failed) cancelAcquire(node);
}
}
acquireShared
共享锁与独占锁的区别
- 独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。
- 共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。
- 如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为锁是独占的。
- 当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程
流程
- 创建的节点不同。共享锁使用addWaiter(Node.SHARED),所以会创建出想要获取共享锁的节点。而独占锁使用addWaiter(Node.EXCLUSIVE)。
- 获取锁成功后的善后操作不同。共享锁使用setHeadAndPropagate(node, r),因为刚获取共享锁成功后,后面的线程也有可能成功获取,所以需要在一定条件唤醒head后继。而独占锁使用setHead(node)。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) doAcquireShared(arg);
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted) selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node);
}
}
/**
setHead函数只是将刚成为将成为head的节点变成一个dummy node。
而setHeadAndPropagate里也会调用setHead函数。
但是它在一定条件下还可能会调用doReleaseShared
“如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁”。
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) doReleaseShared();
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
/**
* 复原中断状态,虽然这个版本的函数不用响应中断。
* 当acquireQueued返回真时,代表这期间函数曾经检测到过中断状态,并且将中断状态消耗掉了
* 所以需要在退出acquire之前,将中断状态重新设置上。
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
releaseShared(int arg)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 在获取共享锁成功时,也可能会调用到doReleaseShared。
private void doReleaseShared() {
for (; ; ) {
Node h = head;
// 如果队列从来没有初始化过(head为null),或者head就是tail,则直接判断head是否变化过。
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue;
unparkSuccessor(h);
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
continue;
}
}
/*
循环检测到head没有变化时就会退出循环
head变化一定是因为acquire thread被唤醒,之后它成功获取锁,然后setHead设置了新head
保证了只要在某个循环的过程中有线程刚获取了锁且设置了新head,就会再次循环
目的当然是为了再次执行unparkSuccessor(h),即唤醒队列中第一个等待的线程
*/
if (h == head) break;
}
}