目录
字段head,tail,state,方法getState,setState,compareAndSetState,spinForTimeoutThreshold
方法selfInterrupt,parkAndCheckInterrupt,acquriedQueued,5个doAcquireXXXX,4个tryXXXX,isHeldExclusively
方法4个acquireXXX,2个tryAcquireXXX,2个releaseXXX
字段unsafe,5个XXXOffset,4个compareAndSetXXX
简介
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import sun.misc.Unsafe;
/**
* 提供了一个框架来实现阻塞锁和依赖于先进先出(FIFO)等待队列的相关同步器(信号量、事件等)。
* 这个类被设计为大多数依赖于单个原子int值来表示状态的同步器的有用基础。
* 子类必须定义改变这种状态的受保护方法,这些方法根据对象的获取或释放,定义状态的含义。
*
* 有了这些,这个类中的其他方法将执行所有的排队和阻塞机制。
* 子类可以维护其他状态字段,但是只有使用getState、setState和
* compareAndSetState方法进行原子更新的int值才会被同步跟踪。
*
* <p>子类应该定义为非公共的内部帮助类,用于实现其外围类的同步属性。
* 类AbstractQueuedSynchronizer没有实现任何同步接口。
* 相反,它定义了像acquireInterruptibly这样的方法,
* 这些方法可以被具体的锁和相关的同步器适当地调用,来实现它们的公共方法。
*
* <p>这个类支持默认的独占模式和共享模式。
* 当以独占模式获取时,其他线程尝试的获取将无法成功。多线程共享模式获取可能(但不一定)成功。
* 这个类并不“理解”这些区别,除非从机制上讲,
* 当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。
*
* 在不同模式中等待的线程共享相同的FIFO队列。
* 通常,实现子类只支持其中一种模式,但是这两种模式都可以发挥作用,例如在ReadWriteLock中。
* 只支持排他模式或仅支持共享模式的子类不需要定义支持未使用模式的方法。
*
* <p>这个类定义了一个嵌套ConditionObject类作为Condition的实现,由子类支持独占模式,
* 方法isHeldExclusively报告同步是否只对当前线程持有,方法release与当前调用getState值,完全释放该对象,
* 并acquire,鉴于这个保存的状态值,最终该对象恢复到以前被获得的状态。
*
* 没有AbstractQueuedSynchronizer方法创建这样的条件,所以如果不能满足这个约束,就不要使用它。
* 当然,ConditionObject的行为取决于它的同步器实现的语义。
*
* <p>该类为内部队列提供了检查、检测和监视方法,也为条件对象提供了类似的方法。
* 可以根据需要使用AbstractQueuedSynchronizer将它们的同步机制导出到类中。
*
* <p>此类的序列化仅存储底层原子的integer维护状态,因此反序列化的对象具有空线程队列。
* 要求可序列化的典型子类将定义一个readObject方法,在反序列化时将其恢复到已知的初始状态。
*
* <h3>使用</h3>
*
* <p>要使用这个类作为同步器的基础,可以使用getState, setState和/或
* compareAndSetState检查和/或修改同步状态,重新定义以下方法:
*
* <ul>
* <li> {@link #tryAcquire}
* <li> {@link #tryRelease}
* <li> {@link #tryAcquireShared}
* <li> {@link #tryReleaseShared}
* <li> {@link #isHeldExclusively}
* </ul>
*
* 默认情况下,这些方法都会抛出UnsupportedOperationException。
* 这些方法的实现必须是内部线程安全的,通常应该是短的,而不是阻塞的。
* 定义这些方法是使用该类的唯一支持的方法。所有其他方法都声明为final,因为它们不能被独立更改。
*
* <p>您可能还会发现从AbstractOwnableSynchronizer继承的方法对于跟踪拥有独占同步器的线程很有用。
* 我们鼓励您使用它们——这使监视和诊断工具能够帮助用户确定哪些线程持有锁。
*
* <p>即使这个类基于内部的FIFO队列,它也不会自动执行FIFO获取策略。
* 独占同步的核心形式是:
*
* <pre>
* Acquire:
* while (!tryAcquire(arg)) {
* <em>enqueue thread if it is not already queued</em>;
* <em>possibly block current thread</em>;
* }
*
* Release:
* if (tryRelease(arg))
* <em>unblock the first queued thread</em>;
* </pre>
*
* (共享模式类似,但可能涉及级联信号。)
*
* <p id="barging">因为在入队前,调用acquire的检查,一个新acquire的线程,可能会先于其他被阻塞和排队的线程。
* 但是,如果需要的话,你可以定义tryAcquire和/或tryAcquireShared
* 来通过内部调用一个或多个inspection方法来禁用闯入,从而提供一个公平的FIFO获取顺序。
*
* 特别是,大多数公平同步器可以定义tryAcquire来返回false,如果hasQueuedPredecessors
* (一个专门为公平同步器使用的方法)返回true。其他的变化也是可能的。
*
* <p>缺省的抢占(也称为贪心、放弃和保护避免)策略的吞吐量和可伸缩性通常是最高的。
* 虽然这不能保证公平或无饥饿,但允许较早队列的线程在较晚队列的线程之前重新竞争,并且每次重新竞争对进入的线程都有一个不偏的成功机会。
* 此外,虽然acquire在通常意义上不会“旋转”,但它们可能在阻塞之前执行多次调用tryAcquire并穿插其他计算。
*
* 当独占同步只是短暂地保持时,这就提供了旋转的大部分好处,而当独占同步不被保持时,就没有了大部分的责任。
* 如果需要的话,你可以通过前面的调用来获取具有“fast-path”检查的方法,
* 可能是预先检查hasContended 和/或hasQueuedThreads,以便只在同步器不可能竞争的情况下才这样做。
*
* <p>这个类通过专门化同步器的使用范围,为同步提供了高效和可伸缩的基础,
* 这些同步器可以依赖于int状态、获取和释放参数,以及内部的FIFO等待队列。
* 如果这还不够,您可以使用原子类,您自己定制的java.util.Queue队列,LockSupport阻塞支持,从较低的级别构建同步器。
*
* <h3>使用样例</h3>
*
* <p>这是一个不可重入的互斥锁类,它的值为0表示解锁状态,1表示锁定状态。
* 虽然不可重入锁并不严格要求记录当前的拥有者线程,但这个类无论如何都会这样做,让使用更容易监视。
* 它也支持条件和暴露的仪器方法之一:
*
* <pre> {@code
* class Mutex implements Lock, java.io.Serializable {
*
* // Our internal helper class
* private static class Sync extends AbstractQueuedSynchronizer {
* // Reports whether in locked state
* protected boolean isHeldExclusively() {
* return getState() == 1;
* }
*
* // Acquires the lock if state is zero
* public boolean tryAcquire(int acquires) {
* assert acquires == 1; // Otherwise unused
* if (compareAndSetState(0, 1)) {
* setExclusiveOwnerThread(Thread.currentThread());
* return true;
* }
* return false;
* }
*
* // Releases the lock by setting state to zero
* protected boolean tryRelease(int releases) {
* assert releases == 1; // Otherwise unused
* if (getState() == 0) throw new IllegalMonitorStateException();
* setExclusiveOwnerThread(null);
* setState(0);
* return true;
* }
*
* // Provides a Condition
* Condition newCondition() { return new ConditionObject(); }
*
* // Deserializes properly
* private void readObject(ObjectInputStream s)
* throws IOException, ClassNotFoundException {
* s.defaultReadObject();
* setState(0); // reset to unlocked state
* }
* }
*
* // The sync object does all the hard work. We just forward to it.
* private final Sync sync = new Sync();
*
* public void lock() { sync.acquire(1); }
* public boolean tryLock() { return sync.tryAcquire(1); }
* public void unlock() { sync.release(1); }
* public Condition newCondition() { return sync.newCondition(); }
* public boolean isLocked() { return sync.isHeldExclusively(); }
* public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
* public void lockInterruptibly() throws InterruptedException {
* sync.acquireInterruptibly(1);
* }
* public boolean tryLock(long timeout, TimeUnit unit)
* throws InterruptedException {
* return sync.tryAcquireNanos(1, unit.toNanos(timeout));
* }
* }}</pre>
*
* <p>这里有一个类似于CountDownLatch类,只是它只需要一个信号来触发。
* 因为latch是非排他的,所以它使用shared acquire和release方法。
*
* <pre> {@code
* class BooleanLatch {
*
* private static class Sync extends AbstractQueuedSynchronizer {
* boolean isSignalled() { return getState() != 0; }
*
* protected int tryAcquireShared(int ignore) {
* return isSignalled() ? 1 : -1;
* }
*
* protected boolean tryReleaseShared(int ignore) {
* setState(1);
* return true;
* }
* }
*
* private final Sync sync = new Sync();
* public boolean isSignalled() { return sync.isSignalled(); }
* public void signal() { sync.releaseShared(1); }
* public void await() throws InterruptedException {
* sync.acquireSharedInterruptibly(1);
* }
* }}</pre>
*
* @since 1.5
* @author Doug Lea
*/
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
构造函数,内部类Node
private static final long serialVersionUID = 7373984972572414691L;
/**
* 创建一个初始同步状态为零的新的AbstractQueuedSynchronizer实例。
*/
protected AbstractQueuedSynchronizer() { }
/**
* 等待队列节点类。
*
* <p>
* 等待队列是“CLH”(Craig、Landin和hagersten)锁队列的变体。
* CLH锁通常用于自旋锁。相反,我们将它们用于阻塞同步器,
* 但使用相同的基本策略,即在其节点的前驱中保存关于线程的一些控制信息。
*
* 每个节点中的“status”字段跟踪线程是否应该阻塞。
* 当它的前驱释放时,一个节点就被唤醒了。
*
* 否则,队列中的每个节点都充当特定通知样式的监视器,它持有单个等待线程。
* status字段不控制线程是否被授予锁等等。
* 如果线程在队列的第一个位置,它可能会尝试acquire。
* 但是第一并不能保证成功,它只是给了你竞争的权利。
* 所以当前释放的竞争者线程可能需要重新等待。
*
* <p>要进入CLH锁的队列,您需要将其作为新的尾部,进行原子拼接。
* 要退出队列,只需设置head字段。
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
*
* <p>插入CLH队列只需要在“tail”上进行一个原子操作,因此有一个简单的原子点来划分从未排队到排队。
* 类似地,脱队列只涉及更新“头”。
* 然而,节点需要做更多的工作来确定谁是它们的后继节点,部分原因是要处理由于超时和中断而可能发生的取消。
*
* <p>“prev”链接(在原始CLH锁中不使用),主要用于处理取消操作。
* 如果一个节点被取消,它的后继节点(通常)会重新链接到一个未被取消的前节点。
* 关于旋锁的类似力学解释,请参阅Scott和Scherer的论文://www.cs.rochester.edu/u/scott/synchronization/
*
* <p>我们也使用“下一个”链接去执行阻塞机制。
* 每个节点的线程id保存在自己的节点中,
* 因此predecessor通过遍历next链接来确定下一个节点是哪个线程,从而向下一个节点发出唤醒信号。
* 确定后继节点必须避免与新排队的节点竞争,以设置它们的前一个节点的“next”字段。
* 当一个节点的后续节点显示为空时,可以通过从原子更新的“tail”往回检查来解决这个问题。
* (或者,换句话说,下一个链接是一种优化,所以我们通常不需要向后扫描。)
*
* <p>取消为基本算法引入了一些保守性。
* 因为我们必须轮询其他节点的取消,我们可能会忽略一个被取消的节点是在我们前面还是后面。
* 这是通过在取消时总是取消后继来处理的,允许它们稳定在新的前驱上,
* 除非我们能够确定将承担此责任的未取消的前驱。
*
* <p>CLH队列需要一个虚拟头节点来启动。
* 但我们不会在建设中创造它们,因为如果没有竞争,那将是浪费精力。
* 相反,在第一次争用时构造节点并设置头和尾指针。
*
* <p>等待条件的线程使用相同的节点,但使用额外的链接。
* 条件只需要在简单(非并发)的链接队列中链接节点,因为它们只在独占持有时才被访问。
* 在await过程中,一个节点被插入到条件队列中。收到信号后,节点被转移到主队列。
* status字段的特殊值用于标记节点所在的队列。
*
* <p>感谢Dave Dice、Mark Moir、Victor Luchangco、BillScherer和Michael Scott,
* 以及jsr -166专家组的成员,为本课程的设计提供了有帮助的想法、讨论和批评。
*/
static final class Node {
/** 标记,指示一个节点正在共享模式下等待 */
static final Node SHARED = new Node();
/** 标记,指示节点正在独占模式下等待 */
static final Node EXCLUSIVE = null;
/** waitStatus值表示线程已取消 */
static final int CANCELLED = 1;
/** waitStatus的值,表示后继线程需要被释放 */
static final int SIGNAL = -1;
/** waitStatus值,表示线程正在等待条件 */
static final int CONDITION = -2;
/**
* waitStatus值,指示下一个被获取的应该无条件传播
*/
static final int PROPAGATE = -3;
/**
* Status字段,只接受值:
* SIGNAL: 这个节点的后继节点被阻塞(或即将被阻塞)(通过park),所以当前节点必须在释放或取消时,解锁后继节点。
* 为了避免竞争,acquire方法必须首先表明它们需要一个signal,然后重试原子获取,然后在失败时阻塞。
*
* CANCELLED: 该节点因超时或中断而被取消。
* 节点永远不会离开这个状态。
* 特别是,一个被取消节点的线程永远不会再次阻塞。
*
* CONDITION: 线程由于阻塞,进入等待状态。
* 该节点当前在条件队列中。它将不会被用作同步队列节点,直到状态在某个时间点将被设置为0。
* (这里使用这个值与字段的其他用途无关,只是简化了机制。)
*
* PROPAGATE: 处于共享模式下, 下一次的acquire需要无条件传播。
* 一个releaseShared应该传播到其他节点。
* 这被设置为(仅针对头节点)在doReleaseShared,以确保传播继续,即使其他操作已经介入。
*
* 0: 以上值都不是
*
* 数值按数字排列以简化使用。非负值意味着节点不需要信号。
* 所以,大多数代码不需要检查特定的值,只需要检查符号。
*
* 对于正常同步节点,该字段初始化为0;
* 对于条件节点,该字段初始化为CONDITION。
* 它可以使用CAS(或者在可能的情况下,使用无条件的volatile写操作)进行修改。
*/
volatile int waitStatus;
/**
* 链接到前节点,当前节点/线程用于检查waitStatus。
* 在排队时分配,在退出排队时为空(为了GC)。
* 同样的,在取消前一个节点时,我们会在找到一个未取消的节点时短路,
* 因为头节点永远不会被取消:
* 一个节点只有在成功获取后才会成为头节点。
* 被取消的线程永远不会成功获取,并且一个线程只会取消自己,而不会取消任何其他节点。
*/
volatile Node prev;
/**
* 链接的后续节点,当前节点/线程释放时,解锁的节点。
* 在排队时分配,绕过被取消的前驱时调整,并在离开队列时清空(为了GC)。
*
* enq操作直到关联操作完成后才会分配前驱的next字段,所以看到一个空的下一个字段并不一定意味着node在队列的末尾。
* 然而,如果next字段看起来是空的,我们可以从尾部扫描prev的重复检查。
* 取消节点的next字段被设置为节点本身,而不是null,以便isOnSyncQueue更容易操作。
*/
volatile Node next;
/**
* 进入该节点队列的线程。构造时初始化,使用后为空。
*/
volatile Thread thread;
/**
* 链接到等待条件的下一个节点,或特殊值SHARED。
* 因为条件队列只有在独占模式下才能被访问,所以我们只需要一个简单链接的队列来在节点等待条件时容纳它们。
* 然后,它们被转移到队列中进行获取。
* 而且由于条件只能是排他的,我们通过使用特殊值来指定共享模式。
*
* nextWaiter成员则是用来表明当前node的线程是想要获取共享锁还是独占锁。注意,这个成员只是这个作用,不是用来连接双向链表的
*/
Node nextWaiter;
/**
* 如果节点在共享模式下等待,则返回true。
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回上一个节点,如果为null则抛出NullPointerException。
* 当前任不能为空时使用。空检查可以被省略,但是存在帮助VM。
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 用于建立初始头或共享标记
}
Node(Thread thread, Node mode) { // addWaiter使用
// 调用的addWaiter(Node.EXCLUSIVE),然后会调用下面的这版构造器。
// 由此可见,刚放到队尾的node,它的nextWaiter肯定为null,它的waitStatus为0(默认初始化)。
// static final Node EXCLUSIVE = null;
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Condition使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}
字段head,tail,state,方法getState,setState,compareAndSetState,spinForTimeoutThreshold
/**
* 等待队列的头,延迟初始化。
* 除了初始化,它只能通过setHead方法进行修改。
* 注意:如果head存在,它的waitStatus保证不会被取消。
* head如果被初始化了,enq方法中就是一个new Node(),作为dummy头,真正的等待队列头是head.next!
* 节点获取锁后,调用sethead(自己),然后设置自己的thread和prev为null,相当于一个new Node!
*
* AQS中已经为我们实现了一个FIFO的等待队列,它是一个双向链表。
* 由于同步器的state一般不能让所有线程同时获得,所以将这些需要暂时等待的线程包装成一个节点放到队列中去,
* 当获取state的条件满足时,会将这个节点内的线程唤醒,以便它接下来去尝试获取state。
*
* head和tail都是AQS的成员,分别代表队列的头和尾,通过持有这两个成员,相当于AQS也持有了这个队列。
* 注意head节点作为一个dummy node,它的thread成员一定为null。
* head节点的thread成员为null,可以理解为将它的thread成员放到AQS的exclusiveOwnerThread属性上去了,所以它的thread成员为null。
* 即使等待线程只有一个,等待队列中的节点个数也肯定是2个,因为第一个节点总是dummy node。
*/
private transient volatile Node head;
/**
* 等待队列的尾部,延迟初始化。
* 修改方法仅仅通过enq方法,增加了新的等待节点。
*/
private transient volatile Node tail;
/**
* 同步状态。
*
* 同步器有一个state,它代表着当前同步器的状态,它是整个AQS的核心属性。
* 我们平时使用的JUC框架下的常用类比如ReentrantLock,其实它们的方法就是在设置和改变这个state。
* 而之前说的子类需要实现的方法,简单的说,它的实现逻辑也就是在设置和改变这个state。
*
* 独占锁,当state为0时,代表没有线程持有锁。
* 当state为1时,代表有线程持有锁。
* 当state>1时,代表有线程持有该锁,并且重入过该锁。
* 所以state是否为0,可以作为判断是否有线程持有该独占锁的标准。
*/
private volatile int state;
/**
* 返回同步状态的当前值。该操作具有volatile读的内存语义。
*
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* 设置同步状态的值。该操作具有volatile写的内存语义。
*
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* 如果当前状态值等于预期值,则自动将同步状态设置为给定的updated值。
* 该操作具有volatile读写的内存语义。
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// 参见下面的本质的设置来支持这一点
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// 队列工具方法
/**
* 旋转而不是使用有时间限制的阻塞的纳秒数。
* 粗略的估计足以在很短的超时时间内提高响应性。
*/
static final long spinForTimeoutThreshold = 1000L;
方法enq,addWaiter,setHead,unparkSuccessor,doReleaseShared,setHeadAndPropagate,cancelAcquire,shouldParkAfterFailedAcquire
/**
* 将节点插入队列,必要时进行初始化。见上图。
*
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 必须初始化
// 进入if (t == null)分支,tail为null,说明队列为空,head肯定也为null。然后尝试CAS设置head成员。
// 注意,如果队列从来没有初始化过(head、tail为null),那么这个循环至少得执行两次,
// 第一次给队列新建一个空node,第二次进if (t != null)的else分支,把参数node放在空node的后面。
// 根据上一条可知,就算只有一个线程入队,入队完毕后队列将有两个node,第一个node称为dummy node,因为它的thread成员为null;
// 第二个node才算是实际意义的队头,它的thread成员不为null。
// 新建的是空node,它的所有成员都是默认值。thread成员为null,waitStatus为0。之后你会发现,队尾node的waitStatus总是0,因为默认初始化。
// compareAndSetHead作为一个CAS操作只有一个参数,是因为它的实现是unsafe.compareAndSwapObject(this, headOffset, null, update);。
// compareAndSetHead的CAS操作也可能失败,当队列为空时,两个线程同时执行到enq。
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 进入if (t == null)的else分支,tail不为null,说明队列至少有一个dummy node,head肯定也不为null。
// 先令node的prev指向tail,但只有CAS设置node为tail成功后,才会将tail的next指向node,才会将双向链表的指针都正确完成指向。
// compareAndSetTail(t, node)可能失败,当多个线程都执行到了node.prev = t这里。
// enq的尾分叉:如果存在很多个线程都刚好执行到了node.prev = t这里,那么CAS失败的线程不能成功入队,此时它们的prev还暂时指向的旧tail。
// tail <- thread1
// <- thread2
// tail <-> thread1
// <- thread2
// tail <-> thread1 <-> thread2
// prev的有效性:从上图第1步可以看到,此时线程1的node已经是成功放到队尾了,但此时队列却处于一个中间状态,前一个node的next还没有指向队尾呢。
// 此时,如果另一个线程如果通过next指针遍历队列,就会漏掉最后那个node;但如果另一个线程通过tail成员的prev指针遍历队列,就不会漏掉node了。
// prev的有效性也解释了AQS源码里遍历队列时,为什么常常使用tail成员和prev指针来遍历,比如你看unparkSuccessor。
node.prev = t;
if (compareAndSetTail(t, node)) {
// 这个循环只有在compareAndSetTail(t, node)成功时才会退出循环,这就保证了enq最终肯定能将参数node放到队尾。
// 简单总结就是:enq利用了自旋(循环)和CAS操作,保证了node放到队尾。
t.next = node;
return t;
}
}
}
}
/**
* 为当前线程和给定模式创建并进入节点队列。
*
* 既然执行到了addWaiter,说明当前线程第一次执行tryAcquire时失败了。
* 既然获取锁失败了,那么就需要将当前线程包装一个node,放到等待队列的队尾上去,以后锁被释放时别人就会通过这个node来唤醒自己。
*
* 不管是提前return,还是执行完enq再return,当return时,已经是将代表当前线程的node放到队尾了。注意,返回的是,代表当前线程的node。
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
//设置node的线程和模式
// 刚放到队尾的node,它的nextWaiter肯定为null,它的waitStatus为0(默认初始化)。
Node node = new Node(Thread.currentThread(), mode);
// 使用enq的快捷方法,如果CAS操作失败,才会去执行enq
// 尝试插入队尾
Node pred = tail;
if (pred != null) {
// 如果队列不为空
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 执行到这里,有两种情况:
// 1.队列为空。head和tail成员从来没有初始化过
// 2.CAS操作失败。当执行compareAndSetTail时,tail成员已经被修改了
enq(node);
return node;
}
/**
* 将队列头设为node,从而退出队列。仅被acquire方法调用。
* 为了gc和抑制不必要的信号和遍历,也将未使用的字段置空。
*
* @param node the node
*/
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
/**
* 唤醒节点的后续节点(如果存在的话)。
* 调用release,releaseShared,cancelAcquire时,调用此方法
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* 一般为SIGNAL,然后将其设置为0.
* 但允许这次CAS操作失败
*
* 首先会尝试设置状态从小于0变成0。
* 一般可以这样认为,如果head的状态为0,代表head后继线程即将被唤醒,或者已经被唤醒。
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* head后继一般能直接通过next找到,但只有prev是肯定有效的。
* 所以遇到next为null,肯定需要从队尾的prev往前找。
* 遇到next的状态为取消,也需要从队尾的prev往前找。
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// 如果遇到s == null,说明我们遇到一种中间状态,next指针还没有指好。如果遇到s.waitStatus > 0,说明head后继刚取消了。
//上面两种情况,都需要先置s为null,因为真正后继需要通过循环才能找到
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
// 注意循环条件t != null && t != node,它会从队尾一直往前找,直到t是null或t已经到达了node。
// 一般情况下,不会出现t != null,所以,这样循环肯定能找到node之后第一个不是取消状态的节点。
if (t.waitStatus <= 0)
// 得到从tail往前找,距离node最近的,waitStatus<=0的节点
s = t;
}
if (s != null)
// 为s的线程,解除阻塞
LockSupport.unpark(s.thread);
}
/**
* 为共享模式释放动作——唤醒后继并确保传播。
* (注意:在独占模式下,release只相当于调用head的unparkSuccessor,如果它需要signal。)
*
* 这个函数的难点在于,很可能有多个线程同时在同时运行它。
* 比如你创建了一个Semaphore(0),让N个线程执行acquire(),自然这多个线程都会阻塞在acquire()这里,然后你让另一个线程执行release(N)。
* 此时 释放共享锁的线程,肯定在执行doReleaseShared。
*
* 由于 上面这个线程的unparkSuccessor,head后继的代表线程也会唤醒,进而执行doReleaseShared。
* 重复第二步,获取共享锁的线程 又会唤醒 新head后继的代表线程。
* 观察上面过程,有的线程 因为CAS操作失败,或head变化(主要是因为这个),会一直退不出循环。进而,可能会有多个线程都在运行该函数。
*
* 由于共享锁在获取锁和释放锁时,都需要唤醒head后继,所以将其逻辑抽取成一个doReleaseShared的逻辑了。
*
* 想要获取共享锁的线程可能经过acquireShared(int arg) -> doAcquireShared(arg) -> 重复着阻塞和被唤醒(可能是这样) ->
* setHeadAndPropagate(node, r) -> doReleaseShared(),所以,调用doReleaseShared的线程可能是一个刚获取到共享锁的线程。
*
* 而调用releaseShared代表某个线程正要释放共享锁,而它也会调用到doReleaseShared。
* 总之,调用doReleaseShared函数的线程可能有两种:
* 一是 刚获取到共享锁的线程(一定情况下,才调用doReleaseShared);
* 二是 释放共享锁的线程(肯定调用)。
*
* 上面这两种情况:1.刚获取到共享锁 2.释放共享锁。都说明 队列中第一个等待中的node,极有可能也能获取共享锁,所以都需要调用doReleaseShared。
* 队列中第一个等待中的node 即 head的后继,所以doReleaseShared中一定会重点照顾它的。
*
* doReleaseShared会尝试唤醒 head后继的代表线程,如果线程已经唤醒,则仅仅设置PROPAGATE状态。
* 上一条的“尝试唤醒 head后继的代表线程”和“设置PROPAGATE状态”都是CAS操作,如果CAS失败,循环会马上continue并再次尝试。
* 当检测到局部变量h与当前最新head是不同对象时,说明有acquire thread刚获取了锁,那下一个等待线程也很可能可以获取锁,所以不会break,循环继续。
*/
private void doReleaseShared() {
/*
* 确保发布的传播,即使有其他正在进行的acquires/releases。
* 如果head需要信号,则按照通常的方式尝试释放它的后继。
* 但如果没有,则将status设置为PROPAGATE,以确保在释放时继续传播。
* 此外,我们必须循环,以防止在执行此操作时添加新节点。
* 另外,与unparkSuccessor的其他用途不同,我们需要知道CAS重置状态是否失败,如果失败,则重新检查。
*/
for (;;) {
// 逻辑是一个死循环,每次循环中重新读取一次head,然后保存在局部变量h中,再配合if(h == head) break;,这样,循环检测到head没有变化时就会退出循环。
// 注意,head变化一定是因为:acquire thread被唤醒,之后它成功获取锁,然后setHead设置了新head。
// 而且注意,只有通过if(h == head) break;即head不变才能退出循环,不然会执行多次循环。
Node h = head;
if (h != null && h != tail) {
// if (h != null && h != tail)判断队列是否至少有两个node,如果队列从来没有初始化过(head为null),
// 或者head就是tail,那么中间逻辑直接不走,直接判断head是否变化了。
// 如果队列中有两个或以上个node,那么检查局部变量h的状态:
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 如果状态为SIGNAL,说明h的后继是需要被通知的。
// 通过对CAS操作结果取反,将compareAndSetWaitStatus(h, Node.SIGNAL, 0)和unparkSuccessor(h)绑定在了一起。
// 说明了只要head成功得从SIGNAL修改为0,那么head的后继的代表线程肯定会被唤醒了。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 循环检查案例
unparkSuccessor(h);// 如果把h的状态从signal改成0,解锁h的后继节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
// 如果状态为0,说明h的后继所代表的线程已经被唤醒或即将被唤醒,并且这个中间状态即将消失,
// 要么由于acquire thread获取锁失败再次设置head为SIGNAL并再次阻塞,要么由于acquire thread获取锁成功而将自己(head后继)
// 设置为新head并且只要head后继不是队尾,那么新head肯定为SIGNAL。
// 所以设置这种中间状态的head的status为PROPAGATE,让其status又变成负数,这样可能被 被唤醒线程
// (因为正常来讲,被唤醒线程的前驱,也就是head会被设置为0的,所以被唤醒线程发现head不为0,就会知道自己应该去唤醒自己的后继了) 检测到。
// head状态为0的情况
// 如果等待队列中只有一个dummy node(