目录
字段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(它的状态为0),那么head也是tail,且head的状态为0。
// 等待队列中当前只有一个dummy node(它的状态为0),acquire thread获取锁失败了(无论独占还是共享),
// 将当前线程包装成node放到队列中,此时队列中有两个node,但当前线程还没来得及执行一次shouldParkAfterFailedAcquire。
// 此时队列中有多个node,有线程刚释放了锁,刚执行了unparkSuccessor里的if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
// 把head的状态设置为了0,然后唤醒head后继线程,head后继线程获取锁成功,直到head后继线程将自己设置为AQS的新head的这段时间里,head的状态为0。
// 具体地讲,如果是共享锁的话,一定是在调用unparkSuccessor之前就把head的状态变成0了,因为if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))。
// 上面这种情况还可以继续延伸,在“唤醒head后继线程”后,head后继线程唤醒后第一次循环获取锁失败
//(你可能会疑问,上面的场景明明是刚有人释放了锁,为什么这里会失败,因为多线程环境下有可能被别的不公平获取方式插队了),
// 调用shouldParkAfterFailedAcquire又将head设置回SIGNAL了,然后第二次循环开始之前(假设head后继线程此时分出去时间片),
// 又有一个释放锁的线程在执行doReleaseShared里面的compareAndSetWaitStatus(h, Node.SIGNAL, 0)成功并且还unpark了处于唤醒状态的head后继线程,
// 然后第二次循环开始(假设head后继线程此时得到时间片),获取锁成功。
// 注意,如果unpark一个已经唤醒的线程,它的副作用是下一次park这个线程,线程不会阻塞。下下次park线程,才会阻塞。
// head状态为0的情况,属于一种中间状态。
// 这种中间状态将变化为,head状态为SIGNAL,不管acquire thread接下来是获取锁成功还是失败。不过获取锁成功这种情况,需要考虑head后继
//(也就是包装acquire thread的那个node)不是队尾,如果是队尾,那么新head的状态也是为0的了。
continue; // 如果把h的状态从0改成PROPAGATE失败,继续循环
}
// 如果状态为PROPAGATE,直接判断head是否变化。
// 两个continue保证了进入那两个分支后,只有当CAS操作成功后,才可能去执行if(h == head) break;,才可能退出循环。
// if(h == head) break;保证了,只要在某个循环的过程中有线程刚获取了锁且设置了新head,就会再次循环。
// 目的当然是为了再次执行unparkSuccessor(h),即唤醒队列中第一个等待的线程。
if (h == head) // 如果head变了,继续循环
break;
}
}
/**
* 设置队列头,并检查后来者是否可以在共享模式下等待,
* 如果propagate> 0或propagate状态被设置,这样传播。
*
* setHead函数只是将刚成为将成为head的节点变成一个dummy node。
* 而setHeadAndPropagate里也会调用setHead函数。但是它在一定条件下还可能会调用doReleaseShared,
* 看来这就是单词Propagate的由来了,也就是我们一直说的“如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁”。
*
* 此函数被共享锁操作而使用。这个函数用来将传入参数设为队列的新节点,
* 如果传参的后继是共享模式且现在要么 共享锁有剩余(propagate > 0) 要么 PROPAGATE状态被设置,那么调用doReleaseShared。
*
* 入参node所代表的线程一定是当前执行的线程,propagate则代表tryAcquireShared的返回值,由于有if (r >= 0)的保证,propagate必定为>=0,这里返回值的意思是:
* 如果>0,说明我这次获取共享锁成功后,还有剩余共享锁可以获取;如果=0,说明我这次获取共享锁成功后,没有剩余共享锁可以获取。
*
* setHeadAndPropagate函数用来设置新head,并在一定情况下调用doReleaseShared。
* 调用doReleaseShared时,可能会造成acquire thread不必要的唤醒。
* 个人认为,作者这么写,是为了防止一些未知的bug,毕竟当一个线程刚获得共享锁后,它的后继很可能也能获取。
*
* 可以猜想,doReleaseShared的实现必须是无伤大雅的,因为有时调用它是没有必要的。
* PROPAGATE状态存在的意义是它的符号和SIGNAL相同,都是负数,所以能用< 0检测到。
* 因为线程刚被唤醒,但还没设置新head前,当前head的status是0,所以把0变成PROPAGATE,好让被唤醒线程可以检测到。
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
private void setHeadAndPropagate(Node node, int propagate) {
// Node h = head; setHead(node);执行完这两句,h保存了旧的head,但现在head已经变成node了。
Node h = head; // 记录旧头以便在下面的检查
setHead(node);
// h == null和(h = head) == null和s == null是为了防止空指针异常发生的标准写法,但这不代表就一定会发现它们为空的情况。
// 这里的话,h == null和(h = head) == null是不可能成立,因为只要执行过addWaiter,CHL队列至少也会有一个node存在的;
// 但s == null是可能发生的,比如node已经是队列的最后一个节点。
// 看第一个if的判断:
// 如果propagate > 0成立的话,说明还有剩余共享锁可以获取,那么短路后面条件。
// 中间穿插一下doReleaseShared的介绍:它不依靠参数,直接在调用中获取head,并在一定情况unparkSuccessor这个head。
// 但注意,unpark head的后继之后,被唤醒的线程可能因为获取不到共享锁而再次阻塞(见上一章的流程分析)。
// 如果propagate = 0成立的话,说明没有剩余共享锁可以获取了,按理说不需要唤醒后继的。
// 也就是说,很多情况下,调用doReleaseShared,会造成acquire thread不必要的唤醒。
// 之所以说不必要,是因为唤醒后因为没有共享锁可以获取而再次阻塞了。
// 继续看,如果propagate > 0不成立,而h.waitStatus < 0成立。
// 这说明旧head的status<0。但如果你看doReleaseShared的逻辑,会发现在unparkSuccessor之前就会CAS设置head的status为0的,
// 在unparkSuccessor也会进行一次CAS尝试,因为head的status为0代表一种中间状态
//(head的后继代表的线程已经唤醒,但它还没有做完工作),或者代表head是tail。
// 而这里旧head的status<0,只能是由于doReleaseShared里的compareAndSetWaitStatus(h, 0, Node.PROPAGATE)的操作,
// 而且由于当前执行setHeadAndPropagate的线程只会在最后一句才执行doReleaseShared,所以出现这种情况,
// 一定是因为有另一个线程在调用doReleaseShared才能造成,而这很可能是因为在中间状态时,又有人释放了共享锁。
// propagate == 0只能代表当时tryAcquireShared后没有共享锁剩余,但之后的时刻很可能又有共享锁释放出来了。
// 继续看,如果propagate > 0不成立,且h.waitStatus < 0不成立,而第二个h.waitStatus < 0成立。
// 注意,第二个h.waitStatus < 0里的h是新head(很可能就是入参node)。
// 第一个h.waitStatus < 0不成立很正常,因为它一般为0(考虑别的线程可能不会那么碰巧读到一个中间状态)。
// 第二个h.waitStatus < 0成立也很正常,因为只要新head不是队尾,那么新head的status肯定是SIGNAL。所以这种情况只会造成不必要的唤醒。
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
// 不同版本acquire的工具方法
/**
* 取消正在进行的acquire尝试。
*
* 回忆起node有一种CANCELLED状态,看来就是为cancelAcquire(node)准备的了。
*
* 如果闹钟设置成功,且闹钟都在node不是一个dummy node,那么将pred与node后继相连,让node后继继续安心睡觉。
* 如果发现node后继已经是排队的第一个了、发现闹钟没有设置成功、发现闹钟设置成功但闹钟所在节点变空节点,都需要唤醒node后继。
*
* @param node the node
*/
private void cancelAcquire(Node node) {
// 如果node不存在则忽略
if (node == null)
return;
// 先设置node的thread为null
node.thread = null;
// 循环用来跳过无效前驱
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 执行完循环,pred会指向node的有效前驱。
// 当然,如果node的前驱就是有效的。那么就不需要跳过了。
// pred的后继无论如何都需要取消,因为即使前面循环没有执行,
// 现在pred的后继(肯定是参数node)也是一个马上取消掉的node。
// 之后有些CAS操作会尝试修改pred的后继,如果CAS失败,那么说明有别的线程在做
// 取消动作或通知动作,所以当前线程也不需要更多的动作了。
Node predNext = pred.next;
// 这里直接使用赋值操作,而不是CAS操作。
// 如果别的线程在执行这步之后,别的线程将会跳过这个node。
// 如果别的线程在执行这步之前,别的线程还是会将这个node当作有效节点。
node.waitStatus = Node.CANCELLED;
// 注意:调用cancelAcquire后,会从doAcquireXXX方法返回,整个方法执行完成
// node节点自己的状态为cancelled
// 方法执行完后,就不会唤醒在node之后的节点了,所以要让pred.next=node.next,并且pred状态为signal,或者直接唤醒node.next
// 如果node是队尾,那简单。直接设置pred为队尾,然后设置pred的后继为null
// 如果node是队尾,那说明node后面没有节点,也就不存在节点需要去唤醒了。
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 如果node不是队尾,node则有后继。
// 分析node不是队尾的情况,既然不是队尾,说明node后面有节点。如下图所示。
// 这里面又有分支,先直接看进入分支要做什么,再来分析进入分支的条件:
// 在if分支里,去做compareAndSetNext(pred, predNext, next),即修复pred的后继,完善双向链表的指针。但这样做有一个前提,就是需要pred的状态已经是SIGNAL的了。
// 在else分支里,去做unparkSuccessor(node),去唤醒node后继。
// pred predNext otherCancelled node nodeNext
// 从上图可以看到,实际意义上,pred的后继已经变成了node后继了。
// pred != head不成立,那么说明pred就是head,执行else分支。node的后继运气爆棚,因为node自己取消掉了,
// node的后继便成为了等待队列中第一个线程(即成为了head后继),自然需要去唤醒它了(unparkSuccessor(node))。
// pred != head成立,但(ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))不成立,执行else分支。
// 第一个条件成立,说明pred并不是head。
// 第二个条件不成立,里面是||,说明是两个子条件都不成立。有下面情况:
// 如果pred的状态是CANCELLED。这似乎有点矛盾,因为之前的for循环已经判断过pred的状态是 <= 0的了,
// 但你要知道多线程环境下,什么都可能发生。很可能从for循环执行到这里的时候,pred又被取消掉了。
// 考虑到pred如果是head后继的话,那么node后继就一下子成为了队列中第一个线程了,所以还是有必要执行unparkSuccessor(node)。
// 如果pred的状态是 <= 0但还不是SIGNAL,但CAS设置pred的状态为SIGNAL却失败了。
// SIGNAL是作为后继节点被唤醒的标志而存在的,现在居然没有设置成功,所以很有必要执行unparkSuccessor(node)。
// pred != head成立,(ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))也成立,
// 但pred.thread != null不成立,执行else分支。
// 第一个条件成立,说明pred并不是head。
// 第二个条件成立,里面是||,说明是两个子条件要么前真,要么前假后真。
// 如果是前真,那么说明pred的状态已经是SIGNAL。
// 如果是前假后真,那么说明pred的状态是0或PROPAGATE,且接下来的CAS操作也成功了,即成功设置为SIGNAL。
// 第三个条件不成立,说明pred变成了一个dummy node了。要么pred是变成head了,要么pred突然被取消了(执行了node.thread = null)。
// 这两种情况,前者必须执行unparkSuccessor(node),后者只是有必要执行。
// pred != head成立,(ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))也成立,
// 且pred.thread != null也成立,只有这里才执行if分支。说明闹钟设置成功,并且设置的node也不是个空节点,
// 那么只需要将pred与node后继相连即可(compareAndSetNext(pred, predNext, next))。
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)
// pred的next设置为next
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
/**
* 检查并更新未能acquire的节点的状态。如果线程应该阻塞,则返回true。
* 这是所有acquire循环中的主要信号控制。要求pred == node.prev。
*
* shouldPark:指的是 该函数的返回值影响是否可以执行parkAndCheckInterrupt函数。
* 因为shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()这么写,只有当该函数返回true时,才会去执行parkAndCheckInterrupt。
* 注意,parkAndCheckInterrupt函数执行后,当前线程就会被挂起了,也就是我们所说的阻塞。
*
* AfterFailedAcquire:指的是 获取锁失败了才会执行该函数。其实具体指两种情况:
* 1. p == head为false,即当前线程的node的前驱不是head
* 2. 虽然 p == head为true,但parkAndCheckInterrupt返回false了,即当前线程虽然已经排到等待队列的最前面,但获取锁还是失败了。
*
* static final int CANCELLED = 1;
* static final int SIGNAL = -1;
* static final int CONDITION = -2;
* static final int PROPAGATE = -3;
*
* node一共有四种状态,但在独占锁的获取和释放过程中,我们只可能将node的状态变成CANCELLED或SIGNAL,而shouldParkAfterFailedAcquire函数就会把一个node的状态变成SIGNAL。
* 注意,一个node新建的时候,它的waitStatus是默认初始化为0的。
* CANCELLED,一个node的状态是CANCELLED,说明这个node的代表线程已经取消等待了。
*
* SIGNAL,一个node的状态是SIGNAL,说明这个node的后继node的代表线程已经阻塞或马上阻塞(shouldParkAfterFailedAcquire设置前驱为SIGNAL后,
* 下一次的acquireQueued循环可能就会阻塞了,所以说“已经阻塞或马上阻塞”),并且当前node成为head并释放锁时,会根据SIGNAL来唤醒后继node。
* 即SIGNAL是唤醒后继节点的标志。
* 有趣的是,CANCELLED状态与当前node关联,SIGNAL状态与后继node关联。
* 本函数,简单的讲就是,跳过无效前驱,把node的有效前驱(有效是指node不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL,之后便返回true代表马上可以阻塞了。
*
* 由于shouldParkAfterFailedAcquire函数在acquireQueued的调用中处于一个死循环中,
* 且因为shouldParkAfterFailedAcquire函数若返回false,且考虑当前线程一直不能获取到锁的情况,那么此函数必将至少执行两次才能阻塞自己。
* shouldParkAfterFailedAcquire只有在检测到前驱的状态为SIGNAL才能返回true,只有true才会执行到parkAndCheckInterrupt。
* shouldParkAfterFailedAcquire返回false后,进入下一次循环,当前线程又会再次尝试获取锁(p == head && tryAcquire(arg))。
*
* 或者说,每次执行shouldParkAfterFailedAcquire,都说明当前循环 尝试过获取锁了,但失败了。
* 如果刚开始前驱的状态为0,那么需要第一次执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL)返回false并进入下一次循环,
* 第二次才能进入if (ws == Node.SIGNAL)分支,所以说 至少执行两次。
* 死循环保证了node自己被阻塞前,一定能设置前驱为SIGNAL成功的。(考虑当前线程一直不能获取到锁)
*
* 执行shouldParkAfterFailedAcquire的线程,一定是参数node的代表线程,不管是独占锁还是共享锁。
* 执行shouldParkAfterFailedAcquire前,线程在此次循环中,已经尝试过获取锁了,但还是失败了。
*
* 考虑所有情况的话,阻塞在parkAndCheckInterrupt的线程可以在任何时候被唤醒。
*
* 释放锁后,唤醒head后继的条件
* head的状态为0,代表head的后继节点即将被唤醒,或者已经被唤醒。
* head的状态为0,属于一种中间状态。
* 最重要的,head的状态为0,代表有人刚释放了锁。
*
* 在shouldParkAfterFailedAcquire中,如果检测到node的前驱的状态为0,那么设置状态为SIGNAL,但设置状态其实是无所谓的。
* 重点在于,这样shouldParkAfterFailedAcquire会返回false,继续下一次循环,也就能再一次尝试获取锁。
* 按照本章场景,再一次尝试获取锁,肯定能成功。
*
* 检测到0后,一定要设置成SIGNAL的原因:
* 设置前驱状态为SIGNAL,以便当前线程阻塞后,前驱能根据SIGNAL状态来唤醒自己。(具体看章节释放锁后,唤醒head后继的条件)
* 设置成SIGNAL后会返回false的原因:
* 返回false后,下一次循环开始,会重新获取node的前驱,前驱如果就是head,那么还会重新尝试获取锁。这次重新尝试是有必要的,某些场景下,重新尝试获取锁会成功。
*
* 为什么检测到PROPAGATE后,一定要设置成SIGNAL,然后继续下一次循环。直接返回true不行吗
* PROPAGATE只能在使用共享锁的时候出现,并且只可能设置在head上。
* 当获取锁的下一次循环开始后,会发现再一次获取锁,就能成功了。
*
* 对于非队尾节点,如果它的状态为0或PROPAGATE,那么它肯定是head。
* 等待队列中有多个节点时,如果head的状态为0或PROPAGATE,说明head处于一种中间状态,且此时有线程刚才释放锁了。
* 而对于acquire thread来说,如果检测到这种状态,说明再次acquire是极有可能获得到锁的。
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 前驱节点已经设置了SIGNAL,闹钟已经设好,现在我可以安心睡觉(阻塞)了。
* 如果前驱变成了head,并且head的代表线程exclusiveOwnerThread释放了锁,
* 就会来根据这个SIGNAL来唤醒自己
*/
return true;
if (ws > 0) {
/*
* 发现传入的前驱的状态大于0,即CANCELLED。说明前驱节点已经因为超时或响应了中断,而取消了自己。
* 所以需要跨越掉这些CANCELLED节点,直到找到一个<=0的节点,即一个有效节点,再把node和这个有效节点的前驱后继连接好。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 进入这个分支,ws只能是0或PROPAGATE。
* CAS设置ws为SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
方法selfInterrupt,parkAndCheckInterrupt,acquriedQueued,5个doAcquireXXXX,4个tryXXXX,isHeldExclusively
/**
* 中断当前线程的方便方法。
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
/**
* park,然后检查是否中断的方便方法。
*
* 既然shouldParkAfterFailedAcquire已经返回了true,那么我现在可以执行parkAndCheckInterrupt了。
*
* 调用完LockSupport.park(this),当前线程就阻塞在这里,直到有别的线程unpark了当前线程,或者中断了当前线程。
* 而返回的Thread.interrupted()代表当前线程在阻塞的过程中,有没有被别的线程中断过,如果有,则返回true。
* 注意,Thread.interrupted()会消耗掉中断的状态,即第一次执行能返回true,但紧接着第二次执行就只会返回false了。
*
* 如果是别的线程unpark了当前线程,那么调用Thread.interrupted()返回false。
* 如果是别的线程中断了当前线程,那么调用Thread.interrupted()返回true。
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
/*
* 各种各样的acquire方式,在排他性/共享和控制模式中有所不同。
* 它们基本上是一样的,但却有恼人的不同。
* 由于异常机制(包括确保在tryAcquire抛出异常时取消)和其他控制的交互作用,
* 只有少量的分解是可能的,至少不会对性能造成太大的损害。
*/
/**
* 以独占不可中断模式获取已经在队列中的线程。用于条件等待方法和acquire方法。
*
* acquireQueued函数是整个独占锁获取的精华部分,它解释了独占锁获取的整个过程。
* 执行到这个函数,说明:
*
* 当前线程已经执行完了addWaiter方法。
* 传入的node的thread成员就是当前线程。
* 传入的node已经成功入队。(addWaiter的作用)
*
* 简单的说,acquireQueued是利用自旋+tryAcquire“不断”地尝试获取锁,当然实际上如果真的是在死循环来不断调tryAcquire,肯定电脑都卡死了。实际流程是:
* 每次循环都会判断是否可以尝试获取锁(p == head),如果可以,那么尝试(tryAcquire(arg))。
* 如果尝试获取锁成功,那么函数的使命就达到了,执行完相应收尾工作,然后返回。
* 如果 不可以尝试 或者 尝试获取锁却失败了,那么阻塞当前线程(parkAndCheckInterrupt)。
* 如果当前线程被唤醒了,又会重新走这个流程。被唤醒时,是从parkAndCheckInterrupt处唤醒,然后从这里继续往下执行。
*
* 执行acquireQueued的线程是谁?
* 一定是node参数的thread成员,虽然执行过程中,可能会经历不断 阻塞和被唤醒 的过程。
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 为什么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,然后就可以获取成功。
// 为什么刚执行完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),当前线程才会被唤醒。关于释放独占锁,以后再具体讲。
if (p == head && tryAcquire(arg)) {
// 从逻辑中可见,如果当前线程获取锁成功,代表当前线程的node会新设置自己为head,然后将其弄成dummy node,
// 即把node的thread成员清空,但这个被清空的thread成员已经在tryAcquire里将这个thread设置为了exclusiveOwnerThread。
// 可以这么理解,head之所以它的thread成员为null,是因为它的thread成员被放在了exclusiveOwnerThread成员里。
// 使用setHead而非compareAndSetHead,因为此时不需要CAS操作,执行到这里说明当前线程已经获得了独占锁(tryAcquire成功),所以别的线程是不可能同时执行这部分代码的。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果线程不应该阻塞,进入下一个循环
// 如果线程要阻塞,进行阻塞,醒来后,检查interrupt,然后赋值给interrupted,最后获取锁的时候返回interrupted
// head后继当初是阻塞在了parkAndCheckInterrupt()这里,根据它的返回值,我们将设置interrupted变量,以在返回用户代码之前,补上中断状态。
// 被唤醒的线程将开始下一次循环,最重要的,会再一次执行p == head && tryAcquire(arg),考虑本文的流程且没有别的线程竞争的话,此处的tryAcquire必定能成功。
interrupted = true; // 这里设置后,会被永久保留
}
} finally {
// finally块是否会执行cancelAcquire(node)?
// 虽然号称此函数是不响应中断的函数,但不响应中断只是对于AQS的使用者来说,如果一个线程阻塞在parkAndCheckInterrupt这里,
// 别的线程来中断它,它是会马上唤醒的,然后继续这个循环。不过想要退出这个函数,只有通过return interrupted,而前一句就是failed = false,
// 所以finally块里,是永远不会去执行cancelAcquire(node)的。
if (failed)
cancelAcquire(node);
}
}
/**
* 以独占可中断模式获取。
*
* 而doAcquireInterruptibly方法 的相对应的方法为acquireQueued方法。观察两个方法的差别很少:
*
* doAcquireInterruptibly没有返回值,而acquireQueued方法有返回值,代表当前线程是否被中断过。
* acquireQueued方法需要有返回值,因为函数调用返回上层后,需要根据返回值来判断是否需要重新设置中断状态,在返回用户代码之前。
* doAcquireInterruptibly不需要返回值,因为该函数中如果检测到了中断状态,就直接抛出异常就好了。
*
* @param arg the acquire argument
*/
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 {
// doAcquireInterruptibly方法的finally块是可能会执行到cancelAcquire(node)的,而acquireQueued方法不可能去执行cancelAcquire(node)的。
// 在doAcquireInterruptibly方法中,如果线程阻塞在parkAndCheckInterrupt这里后,别的线程来中断阻塞线程,阻塞线程会被唤醒,然后抛出异常。
// 本来抛出异常该函数就马上结束掉的,但由于有finally块,所以在结束掉之前会去执行finally块,并且由于failed为true,则会执行cancelAcquire(node)。
if (failed)
cancelAcquire(node);
}
}
/**
* 在独占时间模式下获取。
*
* 将doAcquireNanos方法 与不响应中断的acquireQueued方法、响应中断的doAcquireInterruptibly方法进行对比,发现差别很少。
* 每次循环都会检查时间是否到达deadline。
* 当剩余时间小于spinForTimeoutThreshold时,则不能调用LockSupport.parkNanos,因为时间太短,反而无法精确控制阻塞时间,所以不如在剩余的时间里一直循环。
*
* LockSupport.parkNanos除了会因为别人的park而唤醒,也会因为别人的中断而唤醒,当然最重要的,时间到了,它自己会唤醒。
* 不管哪种情况,被唤醒后,都会检查中断状态。每个循环都会检查一次。
*
* @param arg the acquire argument
* @param nanosTimeout max wait time
* @return {@code true} if acquired
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)//如果时间小于0,直接返回失败
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();
//小于0,说明死期到了
if (nanosTimeout <= 0L)
return false;
//剩余寿命得大于1ms
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 如果被中断了,报出异常
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 在共享不可中断模式下acquire
*
* 共享锁的doAcquireShared有两处不同:
*
* 创建的节点不同。共享锁使用addWaiter(Node.SHARED),所以会创建出想要获取共享锁的节点。
* 而独占锁使用addWaiter(Node.EXCLUSIVE)。
*
* 获取锁成功后的善后操作不同。共享锁使用setHeadAndPropagate(node, r),因为刚获取共享锁成功后,
* 后面的线程也有可能成功获取,所以需要在一定条件唤醒head后继。而独占锁使用setHead(node)。
*
* @param arg the acquire argument
*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//这件事放到里面来了
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//前驱是head时,才尝试获得共享锁
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
// 如果试图acquire共享成功,设置node为head,并且进行宣传
setHeadAndPropagate(node, r);//独占锁这里调用的是setHead
p.next = null;
if (interrupted)
selfInterrupt();//这件事也放到里面来了
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 在共享可中断模式下acquire
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 在共享时间模式下acquire
*
* @param arg the acquire argument
* @param nanosTimeout max wait time
* @return {@code true} if acquired
*/
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
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);
}
}
// 主要的导出的方法
/**
* 试图以独占模式获取。
* 这个方法应该查询对象的状态是否允许以独占模式获取它,如果允许则获取它。
*
* <p>这个方法总是由执行acquire的线程调用。
* 如果这个方法报告失败,如果这个线程还没有进入队列,
* acquire方法可能会让它进入队列,直到它收到来自其他线程的释放信号。
* 这可以用来实现Lock.tryLock()方法。
*
* <p>缺省实现抛出UnsupportedOperationException。
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return {@code true} if successful. Upon success, this object has
* been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/**
* 试图设置状态以反映独占模式下的释放。
*
* <p>这个方法总是由执行release的线程调用。
*
* <p>默认实现抛出UnsupportedOperationException。
*
* @param arg the release argument. This value is always the one
* passed to a release method, or the current state value upon
* entry to a condition wait. The value is otherwise
* uninterpreted and can represent anything you like.
* @return {@code true} if this object is now in a fully released
* state, so that any waiting threads may attempt to acquire;
* and {@code false} otherwise.
* @throws IllegalMonitorStateException if releasing would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/**
* 试图以共享模式获取。这个方法应该查询对象的状态
* 是否允许在共享模式中获取它,如果允许,就获取它。
*
* <p>这个方法总是由执行acquire的线程调用。
* 如果这个方法报告失败,如果这个线程还没有进入队列,
* acquire方法可能会让它进入队列,直到它收到来自其他线程的释放信号。
*
* <p>默认实现抛出UnsupportedOperationException。
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return a negative value on failure; zero if acquisition in shared
* mode succeeded but no subsequent shared-mode acquire can
* succeed; and a positive value if acquisition in shared
* mode succeeded and subsequent shared-mode acquires might
* also succeed, in which case a subsequent waiting thread
* must check availability. (Support for three different
* return values enables this method to be used in contexts
* where acquires only sometimes act exclusively.) Upon
* success, this object has been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if shared mode is not supported
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
/**
* 尝试设置状态以反映共享模式下的释放。
*
* <p>这个方法总是由执行release的线程调用。
*
* <p>默认实现抛出UnsupportedOperationException。
*
* @param arg the release argument. This value is always the one
* passed to a release method, or the current state value upon
* entry to a condition wait. The value is otherwise
* uninterpreted and can represent anything you like.
* @return {@code true} if this release of shared mode may permit a
* waiting acquire (shared or exclusive) to succeed; and
* {@code false} otherwise
* @throws IllegalMonitorStateException if releasing would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if shared mode is not supported
*/
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/**
* 如果同步是针对当前(调用)线程独占持有的,则返回true。
* 每次调用非等待的ConditionObject方法时都会调用这个方法。(等待方法调用release。)
*
* <p>默认实现抛出UnsupportedOperationException。
* 此方法仅在条件对象方法内部调用,因此如果不使用条件,则不需要定义。
*
* @return {@code true} if synchronization is held exclusively;
* {@code false} otherwise
* @throws UnsupportedOperationException if conditions are not supported
*/
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
方法4个acquireXXX,2个tryAcquireXXX,2个releaseXXX
/**
* 以独占模式获取,忽略中断。
* 通过至少调用一次tryAcquire来实现,成功时返回。
* 否则,线程将进入队列,可能会反复阻塞和解除阻塞,调用tryAcquire直到成功。
* 这个方法可以用来实现Lock.lock方法。
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
// 先试图获取锁
// 如果失败,先创建一个独占的节点,加入队列尾部
// 然后在队列中,阻塞,然后被唤醒,然后如果前节点为head,则重新试图获得锁,如果失败,可能再次阻塞。
// 如果在队列中被中断过,则进行中断
// tryAcquire用来获取独占锁一次,try的意思就是只试一次,要么成功,要么失败。
// tryAcquire返回true时,后面的三个函数都不会执行了。
// tryAcquire返回false时,必将调用addWaiter和acquireQueued。
// addWaiter是AQS的实现,因为开始获取锁失败了(tryAcquire返回false),所以需要把当前线程包装成node放到等待队列中,返回代表当前线程的node。
// acquireQueued是AQS的实现,简单的理解就是:用一个死循环不断得去执行tryAcquire,直到获取锁。当然:
// 程序不会真的在死循环,一般情况下,会不断经历 阻塞和被唤醒 两种状态。
// 当参数node的前驱是head时,才会去tryAcquire尝试获得锁。
// selfInterrupt用来复原中断状态,虽然这个版本的函数不用响应中断。当acquireQueued返回真时,代表这期间函数曾经检测到过中断状态,
// 并且将中断状态消耗掉了(Thread.interrupted()),所以需要在退出acquire之前,将中断状态重新设置上。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* 以独占模式获取,如果中断将中止。
* 首先检查中断状态,然后至少调用一次tryAcquire,成功则返回。
* 否则,线程将进入队列,可能会重复地阻塞和解除阻塞,调用tryAcquire直到成功或线程被中断。
* 这个方法可以用来实现Lock.lockInterruptibly方法。
*
* 从下面函数中,可以看到函数都会抛出InterruptedException异常,这是我们能看到的第一处不同。
*
* acquireInterruptibly这个方法与 不响应中断的acquire方法 对应。
* 同样的,进入这个方法后,会第一次进行tryAcquire尝试。
* 但不同的,此acquireInterruptibly函数中,会去检测Thread.interrupted(),并抛出异常。
* 但注意,对于acquireInterruptibly这个方法而言,既可以是公平的,也可以是不公平的,
* 这完全取决于tryAcquire的实现(即取决于ReentrantLock当初是怎么构造的)。
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
* @throws InterruptedException if the current thread is interrupted
*/
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
/**
* 尝试以独占模式获取,如果被中断,则中止,如果给定超时,则失败。
* 首先检查中断状态,然后至少调用一次tryAcquire,成功时返回。
* 否则,线程将进入队列,可能会反复阻塞和解除阻塞,调用tryAcquire直到成功或线程被中断或超时。
* 这个方法可以用来实现Lock.tryLock(long, TimeUnit)。
*
* tryAcquireNanos这个方法与 不响应中断的acquire方法 对应。
* 同样的,进入这个方法后,会第一次进行tryAcquire尝试。
* 但不同的,此tryAcquireNanos函数中,会先去检测Thread.interrupted(),并抛出异常。
*
* 但注意,对于tryAcquireNanos这个方法而言,既可以是公平的,也可以是不公平的,
* 这完全取决于tryAcquire的实现(即取决于ReentrantLock当初是怎么构造的)。
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
* @param nanosTimeout the maximum number of nanoseconds to wait
* @return {@code true} if acquired; {@code false} if timed out
* @throws InterruptedException if the current thread is interrupted
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
/**
* 在独占模式下释放。通过解除一个或多个线程的阻塞来实现,如果tryRelease返回true。
* 此方法可用于实现Lock.unlock方法。
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁tryRelease,这是子类的实现。
Node h = head;
// h != null标准地防止空指针,不过这种情况肯定也有,比如从头到尾都只有一个线程在使用锁,那么队列也不会初始化,head肯定为null。
// 使用h.waitStatus != 0作为唤醒head后继的判断标准,当队列只有一个dummy node时,它的状态为0,也就不会执行unparkSuccessor(h)了。
// 当head的状态为SIGNAL时,说明head后继已经设置了闹钟,会执行unparkSuccessor(h)。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 以共享模式获取,忽略中断。
* 首先调用至少一次tryAcquireShared,成功后返回。
* 否则,线程将进入队列,可能会反复阻塞和解除阻塞,调用tryAcquireShared直到成功。
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquireShared} but is otherwise uninterpreted
* and can represent anything you like.
*/
public final void acquireShared(int arg) {
// 咋一看感觉差别有点大,其实我们被迷惑了,后面我们会发现,
// 之所以acquireShared里没有显式调用addWaiter和selfInterrupt,是因为这两件事都被放到了doAcquireShared(arg)的逻辑里面了。
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/**
* 以共享模式获取,如果中断将中止。
* 首先检查中断状态,然后至少调用一次tryAcquireShared,成功时返回。
* 否则,线程将进入队列,可能会反复阻塞和解除阻塞,调用tryAcquireShared直到成功或线程被中断。
*
* @param arg the acquire argument.
* This value is conveyed to {@link #tryAcquireShared} but is
* otherwise uninterpreted and can represent anything
* you like.
* @throws InterruptedException if the current thread is interrupted
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
/**
* 尝试以共享模式获取,如果被中断,将中止,如果给定超时,将失败。
* 首先检查中断状态,然后至少调用一次tryAcquireShared,成功时返回。
* 否则,线程将进入队列,可能会反复阻塞和解除阻塞,调用tryAcquireShared直到成功或线程被中断或超时。
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquireShared} but is otherwise uninterpreted
* and can represent anything you like.
* @param nanosTimeout the maximum number of nanoseconds to wait
* @return {@code true} if acquired; {@code false} if timed out
* @throws InterruptedException if the current thread is interrupted
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
/**
* 在共享模式下释放。
* 通过解除一个或多个线程的阻塞来实现,如果tryReleaseShared返回true。
*
* 独占锁的逻辑比较简单,只是在head状态不为0时,就唤醒head后继。
*
* 而共享锁的逻辑则直接调用了tryReleaseShared,但在获取共享锁成功时,也可能会调用到doReleaseShared。
* 也就是说,获取共享锁的线程(分为:已经获取到的线程 即执行setHeadAndPropagate中、
* 等待获取中的线程 即阻塞在shouldParkAfterFailedAcquire里)
* 和释放共享锁的线程 可能在同时执行这个doReleaseShared。
*
* @param arg the release argument. This value is conveyed to
* {@link #tryReleaseShared} but is otherwise uninterpreted
* and can represent anything you like.
* @return the value returned from {@link #tryReleaseShared}
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
方法hasQueuedThreads,hasContended,getFirstQueuedThread,fullGetFirstQueuedThread,isQueued,apparentlyFirstQueuedIsExclusive,hasQueuedPredecessors
// 队列检验方法
/**
* 查询是否有等待获取的线程。
* 请注意,由于中断和超时导致的取消可能在任何时候发生,返回true并不保证任何其他线程会acquire。
*
* <p>在这个实现中,这个操作在常量时间返回。
*
* @return {@code true} if there may be other threads waiting to acquire
*/
public final boolean hasQueuedThreads() {
return head != tail;
}
/**
* 查询是否有线程竞争获取这个同步器;既是是否有一个已经阻塞的acquire方法。
*
* <p>在这个实现中,这个操作在常量时间返回。
*
* @return {@code true} if there has ever been contention
*/
public final boolean hasContended() {
return head != null;
}
/**
* 返回队列中第一个(等待时间最长的)线程,如果当前没有线程排队,则返回null。
*
* <p>在这个实现中,这个操作通常在常量时间内返回,
* 但如果其他线程并发地修改队列,则可能在争用时进行迭代。
*
* @return the first (longest-waiting) thread in the queue, or
* {@code null} if no threads are currently queued
*/
public final Thread getFirstQueuedThread() {
// handle only fast path, else relay
return (head == tail) ? null : fullGetFirstQueuedThread();
}
/**
* 当fastpath失败时调用的getFirstQueuedThread版本
*/
private Thread fullGetFirstQueuedThread() {
/*
* 第一个节点通常是head.next。
* 尝试获取它的线程字段,确保一致的读取:
* 如果线程字段为空或s.prev不再是head,那么一些其他线程(s)在我们的两次读取之间,并发执行setHead。
* 在进行遍历之前,我们尝试了两次。
*/
Node h, s;
Thread st;
if (((h = head) != null && (s = h.next) != null &&
s.prev == head && (st = s.thread) != null) ||
((h = head) != null && (s = h.next) != null &&
s.prev == head && (st = s.thread) != null))
return st;
/*
* Head的next字段可能还没有设置,或者可能在setHead之后没有设置。
* 所以我们必须检查tail是否是第一个结点。
* 如果没有,我们继续,安全地从尾部回到头部找到第一个,保证终止。
*/
Node t = tail;
Thread firstThread = null;
while (t != null && t != head) {
Thread tt = t.thread;
if (tt != null)
firstThread = tt;
t = t.prev;
}
return firstThread;
}
/**
* 如果给定的线程当前正在排队,则返回true。
*
* <p>这个实现遍历队列以确定给定线程的存在。
*
* @param thread the thread
* @return {@code true} if the given thread is on the queue
* @throws NullPointerException if the thread is null
*/
public final boolean isQueued(Thread thread) {
if (thread == null)
throw new NullPointerException();
for (Node p = tail; p != null; p = p.prev)
if (p.thread == thread)
return true;
return false;
}
/**
* 如果第一个排队的线程(如果存在的话)正在排他模式等待,则返回true。
* 如果这个方法返回true,并且当前线程正在尝试获取共享模式
* (也就是说,这个方法是从tryAcquireShared调用的),那么它保证当前线程不是第一个排队的线程。
* 仅作为一个启发式ReentrantReadWriteLock的NonfairSync的readerShouldBlock方法调用。
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
/**
* 查询是否有线程等待获取的时间长于当前线程。
*
* <p>调用此方法等价于(但可能个更有效):
* <pre> {@code
* getFirstQueuedThread() != Thread.currentThread() &&
* hasQueuedThreads()}</pre>
*
* <p>注意,由于中断和超时导致的取消可能在任何时候发生,返回true并不保证其他线程会在当前线程之前acquire。
* 同样,由于该队列为空,在该方法返回false后,另一个线程也可能赢得竞争,进入队列。
*
* <p>这种方法被设计用于公平同步器,以避免barging。
* 这样一个同步器的tryAcquire方法应该返回false,
* 如果这个方法返回true,tryAcquireShared方法应该返回一个负值(除非这是一个可重入的获取)。
* 例如,一个公平的、可重入的、独占的模式同步器的tryAcquire方法可能是这样的:
*
* <pre> {@code
* protected boolean tryAcquire(int arg) {
* if (isHeldExclusively()) {
* // A reentrant acquire; increment hold count
* return true;
* } else if (hasQueuedPredecessors()) {
* return false;
* } else {
* // try to acquire normally
* }
* }}</pre>
*
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
*/
public final boolean hasQueuedPredecessors() {
// 要分析return的逻辑,必须要看线程获取同步器state失败时(tryAcquire(arg)返回false),要执行的入队操作addWaiter。
// 首先解释下,hasQueuedPredecessors在先后读取完tail和head后,如果这二者只有一个为null(另一个不为null),那么只可能出现“head不为null,tail为null”的情况:
// 从if (compareAndSetHead(new Node()))到tail = head;的间隙可知,除非一个线程恰好在tail = head;
// 之前读取了tali域(Node t = tail; Node t = head;),那么才可能发生 此时 head不为null,tail为null的情况。
// 否则,其他情况下。head和tail要都为null,要么都不为null。
// 接下来分析hasQueuedPredecessors的返回判断。首先要知道,hasQueuedPredecessors返回true代表有别的线程在CHL队列中排了当前线程之前;
// 返回false代表当前线程处于CHL队列的第一个线程。
// 分析h != t返回false的情况。此时hasQueuedPredecessors返回false。
// 当h和t都为null,返回false。此时说明队列为空,还从来没有Node入过队。
// 当h和t都指向同一个Node,也返回false。此时说明队列中只有一个dummy node,那说明没有线程在队列中。
// 分析h != t返回true,且(s = h.next) == null返回true,直接短路后面。此时hasQueuedPredecessors返回true。
// 既然h != t返回true,说明h和t不相等,先考虑特殊情况(上面讲到的出现“head不为null,tail为null”的情况,此时head是空node,next成员肯定为null),
// 那么说明有一个线程正在执行enq,且它正好执行到if (compareAndSetHead(new Node()))到tail = head;的间隙。
// 但这个线程肯定不是当前线程,所以不用判断后面短路的s.thread != Thread.currentThread()了,
// 因为当前线程连enq都没开始执行,但另一个线程都开始执行enq了,那不就是说明当前线程排在别人后面了,别的线程马上就要入队了。
// 既然h != t返回true,说明h和t不相等,再考虑二者都不为null。那此时队列中已经至少有一个等待中的线程了,那说明当前线程肯定排在别人后面了。
// 分析h != t返回true,且(s = h.next) == null返回false,且s.thread != Thread.currentThread()返回true。此时hasQueuedPredecessors返回true。
// 如果s.thread != Thread.currentThread()返回false。此时hasQueuedPredecessors返回false。
// 现在知道head不为null,而且head.next也不为null了((s = h.next) == null返回false)。
// 我们也知道队列中第一个等待的线程存放在head.next里(注意,head为dummy node,不存放线程),
// 那么如果head.next的线程不是当前线程,那即说明当前线程已经排在别人线程后面了。
// 为什么先读取tail,再读取head
// 前面说到,hasQueuedPredecessors在先后读取完tail和head后,如果这二者只有一个为null(另一个不为null),那么只可能出现“head不为null,tail为null”的情况。
// enq执行
// if (compareAndSetHead(new Node())) // 1
// tail = head; // 2
// 读tail 读head set head set tail 2个null
// 读tail set head set tail 读head tail为null head有值
// set head set tail 读tail 读head 2个有值
// 从而避免了(s = h.next) == null这里的空指针异常。
// 如果考虑指令重排序,那么可能 就变成了 先读取head再读取tail 了,那就可能不对了。
// 但由于这两句都是volatile写操作,每个volatile写操作前面加StoreStore屏障,后面加StoreLoad内存屏障。
// StoreStore 屏障:保证在 volatile 写之前,其前面的所有普通写操作,都已经刷新到主内存中。
// StoreLoad 屏障:避免 volatile 写,与后面可能有的 volatile 读 / 写操作重排序。
// 根据StoreLoad屏障的作用,我们可以保证线程是 先读取tail后读取head。
// 虚假返回true
// 注意,由于中断和超时导致的取消可能随时发生,因此返回true不能保证某些其他线程将在当前线程之前获取。
// 比如当你调用了acquireInterruptibly时,如果因为获取不到锁,会暂时阻塞在parkAndCheckInterrupt里。
// 现在,阻塞在parkAndCheckInterrupt里的线程已经被唤醒,但在此之前被设置了中断状态,所以马上抛出InterruptedException。
// 然后再执行cancelAcquire,被唤醒的线程的所在的node才会被移除出队列。
// 但在cancelAcquire执行完之前,head.next则还是一个即将被移除的node。
// 而此时 别的线程执行hasQueuedPredecessors肯定返回true,所以说它是虚假的true(考虑队列只有dummy node和一个即将被移除的node)。
// 虚假返回false
// 同样,由于队列为空,此方法返回false后,另一个线程也有可能赢得竞争。
// 这个情况比较容易想到,当队列为空时(连dummy node都没有),同时有两个线程正在执行tryAcquire,且两个线程都刚执行完了hasQueuedPredecessors。
// 此时两个线程获得的hasQueuedPredecessors的返回值肯定都是false。
// 但接下来的CAS操作compareAndSetState(0, acquires),就不一定是谁胜出了。
// 失败了那一方,就认为 之前调用hasQueuedPredecessors返回的false,是一个虚假的false。
// 当然针对这种虚假的false,是不会有什么坏影响的,因为acquireQueued中会再次执行tryAcquire的。
Node t = tail; // 按照反向初始化顺序读取字段
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
方法getQueueLength,getQueuedThreads,getExclusiveQueuedThreads,getSharedQueuedThreads,toString
// 仪器和监测方法
/**
* 返回等待获取的线程数的估计数。
* 这个值只是一个估计值,因为在这个方法遍历内部数据结构时,线程数可能会动态改变。
* 此方法设计用于监控系统状态,而不是用于同步控制。
*
* @return the estimated number of threads waiting to acquire
*/
public final int getQueueLength() {
int n = 0;
for (Node p = tail; p != null; p = p.prev) {
if (p.thread != null)
++n;
}
return n;
}
/**
* 返回一个包含可能正在等待获取的线程的集合。
* 因为实际的线程集可能会在构造这个结果时动态改变,所以返回的collection只是一个最佳效果的估计。
* 返回集合的元素没有特定的顺序。
* 这种方法的设计是为了方便构造提供更广泛监控设施的子类。
*
* @return the collection of threads
*/
public final Collection<Thread> getQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
return list;
}
/**
* 返回一个集合,其中包含可能正在等待以独占模式获取的线程。
* 它具有与getQueuedThreads相同的属性,只是它只返回那些由于独占获取而等待的线程。
*
* @return the collection of threads
*/
public final Collection<Thread> getExclusiveQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
if (!p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
}
return list;
}
/**
* 返回一个包含可能在共享模式下等待获取的线程的集合。
* 它具有与getQueuedThreads相同的属性,只是它只返回那些由于共享获取而等待的线程。
*
* @return the collection of threads
*/
public final Collection<Thread> getSharedQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
if (p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
}
return list;
}
/**
* 返回标识此同步器及其状态的字符串。
* 方括号中的state包含字符串" state =",后面跟着getState的当前值,
* 以及"nonempty"或"empty",这取决于队列是否为空。
*
* @return a string identifying this synchronizer, as well as its state
*/
public String toString() {
int s = getState();
String q = hasQueuedThreads() ? "non" : "";
return super.toString() +
"[State = " + s + ", " + q + "empty queue]";
}
方法isOnSyncQueue,findNodeFromTail,transferForSignal,transferAfterCancelledWait,fullyRelease,owns,hasWaiters,getWaitQueueLength,getWaitingThreads
// 内部支撑方法为条件
/**
* 如果一个节点(总是最初放置在条件队列中的节点)
* 现在正在同步队列上等待重新获取,则返回true。
*
* 在执行完fullyRelease后的这一段时间里,当前线程是没有持有锁的了,因为锁已经被自己给释放了。
* 更重要的是,接下来的这一段时间里,另一个线程可能又获得了锁,然后开始执行await \ signal \ unlock,即接下来得考虑多线程了。
*
* 回到await的逻辑,现在要进入循环了。
* 循环里马上就会调用LockSupport.park(this);阻塞当前线程,这也就是本章大标题说的“await()第一次调用park之前”的时间点了。
*
* 但是每次循环都会判断一下循环条件!isOnSyncQueue(node),即当前线程node不在同步队列中。
* 很明显,如果是第一次进入循环,这个循环条件肯定会满足的,因为我们刚刚才执行了addConditionWaiter将当前线程node加入到条件队列中呢。
*
* 这个循环条件!isOnSyncQueue(node)主要是为了当前线程被唤醒后,进行必要的判断。
*
* 简单的说,isOnSyncQueue判断节点已经入队同步队列的标准,必须是node已经成为队尾(包括当前是队尾,或者曾经是队尾)。
*
* @param node the node
* @return true if is reacquiring
*/
final boolean isOnSyncQueue(Node node) {
//如果node状态为CONDITION,或者虽然node状态不为CONDITION,但node前驱为空
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//执行到这里,说明上面两个条件都不成立:
//1. node状态不为CONDITION 2. node前驱不为空
//如果node后继不为空,说明已经在sync queue
if (node.next != null) // 如果有继承者,它必须在队列中
return true;
//执行到这里,说明:
//前一个时间点检测到,1. node状态不为CONDITION 2. node前驱不为空
//后一个时间点检测到:node后继为空
//现在发现node处于一个状态:前驱不为空,但后继为空。如果node是当前队尾肯定也是这种状态
//但enq进队尾时CAS设置tail失败时,也会是这种状态。所以需要从尾到头检测一遍。
return findNodeFromTail(node);
}
/**
* 如果节点处于同步队列中,则通过从tail向后搜索返回true。仅在isOnSyncQueue需要时调用。
*
* @return true if present
*/
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
/**
* 将节点从条件队列转移到同步队列。如果成功返回true。
*
* transferForSignal函数简单的说,就是为让参数node入队,如果入队成功就返回true。
*
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* 如果失败,说明node代表线程因中断而已经执行了中断流程中的compareAndSetWaitStatus(node, Node.CONDITION, 0)
*
* 这也间接说明了signal流程和中断流程都是以 成功设置node状态 作为标准,哪个流程成功了,哪个流程就把node入队同步队列,从而以免重复入队。
* (这一点需要和后面的中断流程内容联动,如果难以理解,可以直接往下看)
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 如果CAS设置成功,那么enq(node)入队,然后肯定返回true。但是注意,在一定条件下,会唤醒node代表线程。注意enq(node)返回node入队后的前驱prev。
*
* 这个一定条件是指,node的前驱状态是同步队列节点的取消状态,或者状态<=0但CAS设置前驱状态为SIGNAL失败了。
* 如果上面条件发生了,就直接唤醒node线程。这里我们回想一下await()第一次调用park之后这个时间点,
* 现在node线程终于被唤醒,假设没有中断发生过的话,不会因break退出循环,再一次检测!isOnSyncQueue(node)会发生条件不成立
* (node已经因为enq(node)而成功入队)。然后又会走到独占锁获得过程中的acquireQueued函数。
*
* 唤醒node代表线程不一定代表它接下来能够获得锁,但是我们也不用担心这会有什么坏影响,因为acquireQueued函数自己会去做判断,
* 如果发现还是获取不到锁的话,则会调用shouldParkAfterFailedAcquire将node的前驱设置为SIGNAL的。
* 总之,compareAndSetWaitStatus(p, ws, Node.SIGNAL)直接保证了node的前驱状态为SIGNAL,
* 而LockSupport.unpark(node.thread)间接保证了node的前驱状态为SIGNAL,之所以说间接,是因为这不是在signal线程里做的,而是通过唤醒node线程做到的。
*/
Node p = enq(node);
int ws = p.waitStatus;
// p是node的前驱,设置p的状态为signal
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
/**
* 传输节点,如果有必要,在取消等待后同步队列。
* 如果线程在被通知之前被取消(超出了时间限制或者被中断),则返回true。
*
* 首先通过CAS设置node状态从CONDITION变成0,如果成功了,就将node入队同步队列,然后直接返回true。
* 注意,这里的CAS操作和transferForSignal里的compareAndSetWaitStatus(node, Node.CONDITION, 0)是一样的,也就是说:
* 执行signal的线程 和 因中断被唤醒的node线程,在这句compareAndSetWaitStatus(node, Node.CONDITION, 0)
* 这里有竞争关系,谁竞争赢了,谁才有资格将node入队同步队列enq(node)。
*
* 这样的竞争关系是很必要的,直接避免两个线程将同一个node重复入队。
*
* 通过CAS设置node状态从CONDITION变成0,如果失败了,那就不能再执行enq(node)啦。
* 因为肯定有另一个signal线程正在执行enq(node)或者已经执行完了enq(node)了。
*
* 但这里我们如果发现另一个signal线程还没有执行完了enq(node)(通过!isOnSyncQueue(node)条件判断),
* 就必须一直等待,直到另一个signal线程执行完了enq(node),然后循环才可以退出。之所以这么等一下,是因为如果不等,
* node线程自己接下来就会执行到acquireQueued了,而执行acquireQueued的前提就是已经入队同步队列完毕。
*
* 等到node线程执行完了checkInterruptWhileWaiting,考虑本章的中断场景,就会直接break出循环,然后执行到acquireQueued。
* 如果不能获得锁,还是会阻塞在acquireQueued的shouldParkAfterFailedAcquire里。
*
* 总之执行中断流程完毕后此时node的状态为:
*
* 在数据结构上,node还是处于条件队列上(没有执行first.nextWaiter = null),但同时也处于了同步队列上了(enq(node))。
* 不用担心nextWaiter会影响到当前node不被独占锁的释放而唤醒,因为独占锁的释放过程不关心head后继的nextWaiter。
* 在执行过程上,node线程从LockSupport.park(this)这里被中断唤醒,因为有中断状态而break出循环,然后执行acquireQueued。
* 如果不能获得锁,还是会阻塞在acquireQueued的shouldParkAfterFailedAcquire里。
*
*
*
* 因中断被唤醒的node线程 和 signal线程 的竞争关系
*
* 上面说了中断流程和signal流程谁在前面,await的表现也会有所不同。具体的说,则体现在:
* 因中断被唤醒的node线程 和 signal线程 的竞争关系上。这两个线程完全有可能同时在执行中,而它们的竞争点则体现在:
*
* 因中断被唤醒的node线程。transferAfterCancelledWait函数里的compareAndSetWaitStatus(node, Node.CONDITION, 0)。
* signal线程。transferForSignal函数里的compareAndSetWaitStatus(node, Node.CONDITION, 0)。
* 这两个transfer方法都会执行同一个CAS操作,但很明显,只能有一个线程能够执行CAS操作成功。
*
* 竞争成功的那一方,transfer方法会返回true,并且会执行enq(node)。
* 竞争失败的那一方,transfer方法会返回false,并且不会执行enq(node)。
* 当一个处于条件队列上的node,状态从CONDITION变成0时,就意味着它正在前往同步队列,或者已经放置在同步队列上了。
* 如果transferAfterCancelledWait竞争成功,我们称这个node线程走的是中断流程。
* 如果transferForSignal竞争成功,我们称这个node线程走的是signal流程。
*
* @param node the node
* @return true if cancelled before the node was signalled
*/
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* 如果我们输给了signal(),那么我们就不能继续,直到它完成它的enq()。
* 在不完全转移过程中取消是罕见的,也是短暂的,所以只管旋转。
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
/**
* 使用当前状态值调用release;
* 返回保存的状态。
* 取消节点并在失败时抛出异常。
*
* 在调用fullyRelease之前,当前线程已经被包装成node放到条件队列中去了。
* 注意,在这个函数以后,我们再也不会对firstWaiter和lastWaiter轻举妄动了,
* 因为await()以后的执行过程中,当前线程很长一段时间内是没有持有锁的。
* 我们调用fullyRelease函数来释放当前线程占有的锁。
*
* 该函数很简单,只是通过调用独占锁释放过程中的release函数来释放锁,
* 注意,不管锁被重入了几次,在这里我们都会一次性释放干净的(release(savedState))。
* 这也是为什么这个函数叫做fullyRelease全释放。
*
* 如果真的有哪个傻子在没有获得锁时,调用了await,那么release会抛出异常且导致抛出时failed变量为真,
* 那么在finally块里面就会执行语句,把当前线程的node的状态变成非CONDITION的。
*
* @param node the condition node for this wait
* @return previous sync state
*/
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//这里会获得重入锁的总次数
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// 条件测量方法
/**
* 查询给定的条件对象是否使用此同步器作为其锁。
*
* @param condition the condition
* @return {@code true} if owned
* @throws NullPointerException if the condition is null
*/
public final boolean owns(ConditionObject condition) {
return condition.isOwnedBy(this);
}
/**
* 查询是否有线程正在等待与此同步器关联的给定条件。
* 请注意,因为超时和中断可能在任何时候发生,
* 返回true并不保证将来的信号会唤醒任何线程。该方法主要用于监控系统状态。
*
* @param condition the condition
* @return {@code true} if there are any waiting threads
* @throws IllegalMonitorStateException if exclusive synchronization
* is not held
* @throws IllegalArgumentException if the given condition is
* not associated with this synchronizer
* @throws NullPointerException if the condition is null
*/
public final boolean hasWaiters(ConditionObject condition) {
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.hasWaiters();
}
/**
* 返回等待与此同步器相关的给定条件的线程数的估计数。
* 请注意,因为超时和中断可能在任何时候发生,所以这个估计值仅作为实际等待者数量的上限。
* 这种方法是设计用于监控系统状态,而不是用于同步控制。
*
* @param condition the condition
* @return the estimated number of waiting threads
* @throws IllegalMonitorStateException if exclusive synchronization
* is not held
* @throws IllegalArgumentException if the given condition is
* not associated with this synchronizer
* @throws NullPointerException if the condition is null
*/
public final int getWaitQueueLength(ConditionObject condition) {
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.getWaitQueueLength();
}
/**
* 返回一个集合,其中包含可能正在等待与此同步器关联的给定条件的线程。
* 因为实际的线程集可能会在构造这个结果时动态改变,所以返回的collection只是一个最佳效果的估计。
* 返回集合的元素没有特定的顺序。
*
* @param condition the condition
* @return the collection of threads
* @throws IllegalMonitorStateException if exclusive synchronization
* is not held
* @throws IllegalArgumentException if the given condition is
* not associated with this synchronizer
* @throws NullPointerException if the condition is null
*/
public final Collection<Thread> getWaitingThreads(ConditionObject condition) {
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.getWaitingThreads();
}
内部类ConditionObject
/**
* 作为锁实现基础的AbstractQueuedSynchronizer的条件实现。
*
* <p>这个类的方法文档从锁和条件的用户的角度描述了机制,而不是行为规范。
* 该类的导出版本通常需要附带描述条件语义的文档,这些语义依赖于相关的AbstractQueuedSynchronizer的语义。
*
* <p>这个类是可序列化的,但是所有字段都是暂态的,因此反序列化的条件没有等待者。
*
* 每一个Condition对象都对应到一个条件队列condition queue,而每个线程在执行await()后,都会被包装成一个node放到condition queue中去。
*
* condition queue是一个单向链表,它使用nextWaiter作为链接。
* 这个队列中,不存在dummy node,每个节点都代表一个线程。这个队列的节点的状态,我们只关心状态是否为CONDITION,
* 如果是CONDITION的,说明线程还等待在这个Condition对象上;
* 如果不是CONDITION的,说明这个节点已经前往sync queue了。
*
* 假设现在存在一个Lock对象和通过这个Lock对象生成的若干个Condition对象,
* 从队列上来说,就存在了一个sync queue和若干个与这个sync queue关联的condition queue。
* 本来这两种队列上的节点没有关系,但现在有了signal方法,就会使得condition queue上的节点会跑到sync queue上去。
*
* sync queue null(head) wait1 wait2 waitn(old tail) waitA(new tail)
* condition queue waitA(old firstWaiter) waitB(new firstWaiter) waitC waitZ(lastWaiter)
*
* 上图简单体现了节点从从condition queue转移到sync queue上去的过程。
* 即使是调用signalAll时,节点也是一个一个转移过去的,因为每个节点都需要重新建立sync queue的链接。
* 我们这里可以先简单理解一下关于队列的动作:
*
* 如果一个节点刚入队sync queue,说明这个节点的代表线程没有获得锁(尝试获得锁失败了)。
* 如果一个节点刚出队sync queue(指该节点的代表线程不在同步队列中的任何节点上,
* 因为它已经跑到了AQS的exclusiveOwnerThread成员上去了),说明这个节点的代表线程刚获得了锁(尝试获得锁成功了)。
* 如果一个节点刚入队condition queue,说明这个节点的代表线程此时是有锁了,但即将释放。
* 如果一个节点刚出队condition queue,因为前往的是sync queue,说明这个节点的代表线程此时是没有获得锁的。
*
* ConditionObject又是AQS的一个成员内部类,这意味着不管生成了多少个ConditionObject,
* 它们都持有同一个AQS对象的引用,这和“一个Lock可以对应到多个Condition”相吻合。
*
* 这也意味着:对于同一个AQS来说,只存在一个同步队列sync queue,但可以存在多个条件队列condition queue。
* 成员内部类有一个好处,不管哪个ConditionObject对象都可以调到同一个外部类AQS对象的方法上去。
* 比如acquireQueued方法,这样,不管node在哪个condition queue上,最终它们离开后将要前往的地方总是同一个sync queue。
*
* 执行await的当前线程。这个线程是最开始调用await的线程,也是执行await所有调用链的线程,它被包装进局部变量node中。(后面会以node线程来称呼它)
*
* 执行signal的线程。这个线程会改变await当前线程的node的状态state,使得await当前线程的node前往同步队列,并在一定条件在唤醒await当前线程。
*
* 中断await当前线程的线程。你就当这个线程只是用来唤醒await当前线程,并改变其中断状态。
* 只不过await当前线程它自己被唤醒后,也会做和上一条同样的事情:“使得await当前线程的node前往同步队列”。
*
* 执行unlock的线程。如果await当前线程的node已经是同步队列的head后继,那么获得独占锁的线程在释放锁时,就会唤醒 await当前线程。
* 理解了这几个线程的存在,对于本文的理解有很大帮助。从用户角度来说,执行await \ signal \ unlock的前提都是线程必须已经获得了锁。
*/
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** 条件队列的第一个节点。
*
* firstWaiter和lastWaiter分别代表条件队列的队头和队尾。
* 注意,firstWaiter和lastWaiter都不再需要加volatile来保证可见性了。
* 这是因为源码作者是考虑,使用者肯定是以获得锁的前提下来调用await() / signal()这些方法的,既然有了这个前提,
* 那么对firstWaiter的读写肯定是无竞争的,既然没有竞争也就不需要 CAS+volatile 来实现一个乐观锁了。
* */
private transient Node firstWaiter;
/** 条件队列的最后一个节点。*/
private transient Node lastWaiter;
/**
* 创建一个新的条件对象实例。
*/
public ConditionObject() { }
// Internal methods
/**
* 增加了一个新的等待者到等待队列。
*
* 同步队列sync queue的新建node,它的初始状态为0。而条件队列condition queue的新建node的初始状态为CONDITION。
* sync queue如果拥有队头,队头肯定会是一个dummy node(即线程成员为null)。condition queue则不会有一个dummy node,每个节点的线程成员都不为null。
* sync queue是一个双向链表,需要维持前驱和后继都正确。condition queue只是一个单链表,只需要维持后继即可。
* 这里先提前说下,中断await当前线程的线程(这里特指中断操作在signal之前)和执行signal的线程都会使得条件队列上的node的状态从CONDITION变成0。
*
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// 同步队列中的节点只是CONDITION的,如果不是,就认为是以后将离开条件队列的节点。
// 将调用unlinkCancelledWaiters来一次大清理,并重新获得队尾
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 包装当前线程为node,状态初始为CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//node放到最后
if (t == null)// 如果当前队尾为null,那么整个条件队列都没初始化呢
firstWaiter = node;
else// 如果队列有至少一个节点
t.nextWaiter = node;
lastWaiter = node;
return node;
}
/**
* 删除和传输节点,直到命中非取消的节点或null。
* 从signal中分离出来部分是为了鼓励编译器,关联没有等待者的情况。
*
* 整个逻辑是一个do while循环,不过这个循环一般不会执行多次,只要有一次signal成功了(transferForSignal(first)返回了真)
* 就不会继续循环了,因为这个函数的目的就只是signal一个节点。
* 如果signal失败了,那么获得新队头((first = firstWaiter) != null),只要新队头不为null,则继续下一次循环。
*
* 相比signalAll流程,我们使用的还是这个transferForSignal来做的signal动作,但这里我们终于使用到了transferForSignal的返回值,
* 如果返回了true说明已经signal了一个node了,也就不用继续循环了。
* 总之,signal方法会唤醒条件队列中第一个状态不是取消状态(不是CONDITION)的节点。
*
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {//first参数是队头
do {
// 更新队头,让队头往后移动一位
if ( (firstWaiter = first.nextWaiter) == null)
//如果发现新队头为null,让队尾也为null
lastWaiter = null;
// 老套路,把参数和后面的链表断开
first.nextWaiter = null;
// 将first从条件队列转移到同步队列,如果失败,first=firstWaiter,继续循环,知道firstWaiter为null
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);// transferForSignal失败才会执行这行
// 获得新队头,如果新队头不为null,则继续下一次循环,因为现在一次signal都没有成功呢
}
/**
* 删除和传输所有节点。
*
* 上来就把代表条件队列的队头队尾成员置null,之后别人就无法通过队头队尾找到队列中的节点了,只有当前线程能通过局部变量first来找到队列节点了。
*
* 而接下来不断遍历,直到已经遍历到队尾(first != null)。
* 每次遍历中,将当前遍历节点 与 剩下的条件队列链 断开,然后对当前遍历节点执行transferForSignal。
*
* @param first (non-null) the first node on condition queue
*/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
// 逐个传输
transferForSignal(first);
first = next;
} while (first != null);
}
/**
* unlinkCancelledWaiters函数是用来从头到尾清理状态不为CONDITION的节点的。
*
* 从条件队列中解除被取消的等待节点(等待的时候被中断)的链接。仅在持有锁时调用。
* 当在条件等待期间发生取消时,以及当看到最后一个等待者被取消时插入一个新的等待者时,就会调用这个函数。
* 这个方法是为了避免在没有信号的情况下垃圾保留。
* 因此,即使它可能需要一次完整的遍历,它也只有在没有信号的情况下出现超时或取消时才会发挥作用。
* 它遍历所有节点,而不是在特定目标处停止,以解除指向垃圾节点的所有指针的链接,而不需要在取消风暴期间多次重新遍历。
*/
private void unlinkCancelledWaiters() {
Node t = firstWaiter; //获得队头
Node trail = null; //trail用来保存遍历过程中,最近一次发现的状态为CONDITION的节点
while (t != null) { //只要循环变量不为null,循环继续
Node next = t.nextWaiter; //得到循环变量的后继,循环结束前使用
if (t.waitStatus != Node.CONDITION) {
// 在condition队列中删除t,如果t的状态不是condition
t.nextWaiter = null;//首先使得t与后继断开链接
// 如果直到当前循环都还没有发现一个CONDITION的节点
if (trail == null)
firstWaiter = next;//那么将循环变量的后继,作为新队头
// 如果当前循环之前已经发现了一个CONDITION的节点
else
trail.nextWaiter = next;//那么将trail与next相连,相当于跳过了循环变量t
// 如果已经遍历到队尾,需要将trail作为队尾,因为trail才是队列中最后一个为CONDITION的节点
if (next == null)
lastWaiter = trail;
}
// 如果循环变量为CONDITION,则更新trail
else
trail = t;
t = next;//循环结束前,用next更新循环变量
}
}
// public methods
/**
* 将等待时间最长的线程(如果存在的话)
* 从这种情况的等待队列移动到所属锁的等待队列。
*
* 相比signalAll,signal方法只会唤醒一个node,准确的说,是唤醒从同步队列队头开始的第一个状态为CONDITION的node。
*
* 首先看调用signal的线程是否已经拥有了独占锁,不然就会抛出异常。与之前的doSignalAll不同,这里调用的是doSignal。
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/**
* 将此情况下的所有线程从等待队列移动到所属锁的等待队列。
*
* 简单总结一下signalAll方法:
*
* 将条件队列清空(通过lastWaiter = firstWaiter = null来达到效果,但函数中的局部变量已经保存了队头,且实际上节点的链接还存在着)。
* 遍历每个节点。
* 如果遍历节点已经被取消掉了(compareAndSetWaitStatus(node, Node.CONDITION, 0)失败),那么直接返回,处理下一个节点。
* 如果遍历节点还没取消掉(compareAndSetWaitStatus(node, Node.CONDITION, 0)成功),那么将其入队同步队列。
* 在一定条件下(无法设置node前驱状态为SIGNAL时),还将唤醒node代表线程。然后处理下一个节点。
* 另外注意,signalAll方法直到结束返回,都一直没有释放锁呢(因为没有在signalAll里面执行过release),也就是说,执行signalAll的线程一直都是持有锁的。
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signalAll() {
// 首先会检查执行signalAll的线程是否已经获得了锁,通过判断ExclusiveOwnerThread成员变量。
// 然后判断条件队列是否为空,只要不为空就执行doSignalAll。
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
/**
* 实现不可中断条件等待。
* <ol>
* <li> 保存getState返回的锁状态。
* <li> 以保存的状态作为参数调用release,如果失败则抛出IllegalMonitorStateException。
* <li> 阻塞直到被signal
* <li> 通过调用特定版本的acquire,并将保存的状态作为参数进行重新获取。
* </ol>
*
* 前面介绍的await方法里,中断来临时会使得当前线程离开while循环进而去执行acquireQueued开始抢锁。
* 换句话说,await方法允许,当前线程因为中断而不是因为signal,而最终退出await方法(毕竟acquireQueued最终还是会抢锁成功的)。
* 有时候我们希望,退出await方法的原因,只能是因为signal,所以就需要使用awaitUninterruptibly了。
*
* 可以发现awaitUninterruptibly方法只是对await方法进行了大面积的删改:
*
* 函数开头不需要检查调用前是否被中断过了
* 用来记录中断状态的变量只需要记录两种状态(中断过,或没有中断),所以用boolean变量就够了。
* 在while循环里,当前线程如果只是因为中断而被唤醒,那么消耗掉中断状态(Thread.interrupted())。
* 如果还没有signal线程的到来,那么当前线程的node还是不处于sync queue之上的,所以下次循环继续,然后又阻塞在LockSupport.park(this)这里。
*
* 如果抢锁过程中(acquireQueued(node, savedState))发生过中断,或者抢锁之前发生过中断(|| interrupted),那么就自我中断一下。
* 不需要判断当前线程node在条件队列上的链接是否断开,因为awaitUninterruptibly方法只会因为signal流程会退出while循环,而signal流程肯定会 断开条件队列上的链接的。
* 不需要执行reportInterruptAfterWait了,因为自我中断已经做过了。
* 经过上面分析可以发现,awaitUninterruptibly方法全程都不会响应中断,不管是在抢锁过程之前还是之中发生过中断,都是只是简单地自我中断一下就好了。
*
* 而因为awaitUninterruptibly方法不会去执行checkInterruptWhileWaiting,
* 所以要想满足退出while循环的条件!isOnSyncQueue(node)进而去执行acquireQueued开始抢锁,
* 只能是因为signal流程中执行了transferForSignal(里面执行了enq(node),使得node入队了sync queue)。
* 而从使用者的角度看,中断并不能使得线程从await调用处唤醒,只有执行了signal,线程才能从await调用处唤醒。
*
* 总结一下awaitUninterruptibly方法:
*
* 中断会唤醒当前线程,但当前线程的node还是不处于sync queue之上,所以当前线程马上又会阻塞。
* 只有signal方法才可以使得当前线程的node处于sync queue之上。
* 调用该方法中,如果发生了中断,会在返回用户代码之前,自我中断一下。
*/
public final void awaitUninterruptibly() {
// condition队列中添加节点
Node node = addConditionWaiter();
// 释放锁,解锁head的后驱的 线程
int savedState = fullyRelease(node);
boolean interrupted = false;
// 如果node不在同步队列上,阻塞node
// 直到有人signal该节点,将节点从condition队列放到同步队列,跳出循环
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
// 以独占不可中断模式获取已经在队列中的线程。用于条件等待方法和acquire方法。
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
/*
* 对于可中断等待,我们需要跟踪是否抛出InterruptedException,
* 如果在条件阻塞时被中断,而如果在阻塞等待重新获取时被中断,则重新中断当前线程。
*/
/** 模式意味着退出等待时重新中断 */
private static final int REINTERRUPT = 1;
/** 模式表示在退出等待时抛出InterruptedException */
private static final int THROW_IE = -1;
/**
* 检查中断,如果在被通知之前被中断返回THROW_IE,
* 如果被通知之后重新中断,或者如果没有被中断返回0。
*/
private int checkInterruptWhileWaiting(Node node) {
// Thread.interrupted()首先判断当前线程有没有被中断过,如果没有,那么返回0.
// 如果有中断过,那么通过transferAfterCancelledWait(node)判断返回THROW_IE还是REINTERRUPT.
// checkInterruptWhileWaiting的返回值最终是会赋值给局部变量interruptMode的,它现在有三种可能值:
// 0:代表整个await过程中没有发生过中断。
// THROW_IE:代表await执行完毕返回用户代码处时,需要抛出异常。当中断流程发生在signal流程之前时。
// REINTERRUPT:代表await执行完毕返回用户代码处时,不需要抛出异常,仅需要重新置上中断状态。当signal流程发生在中断流程之前时。
// 之所以THROW_IE和REINTERRUPT两个值所代表的场景需要进行区分,是因为一个线程A因await而进入condition queue后,
// 正常的流程是另一个线程B执行signal或signalAll后才使得线程A的node入队到sync queue。
// 但如果中断流程发生在signal流程之前,也能使得线程A的node入队到sync queue,但这就没有走正常的流程了。
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
/**
* Throws InterruptedException, reinterrupts current thread, or
* does nothing, depending on mode.
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* 实现可中断条件等待。
* <ol>
* <li> 如果当前线程被中断,则抛出InterruptedException。
* <li> 保存getState返回的锁状态。
* <li> 以保存的状态作为参数调用release,如果失败则抛出IllegalMonitorStateException。
* <li> 阻塞直到被signal或者被中断
* <li> 通过调用特定版本的acquire,并将保存的状态作为参数进行重新获取。
* <li> 如果在步骤4中阻塞时被中断,则抛出InterruptedException。
* </ol>
*
* await()对于使用者来说,进入await()时是持有锁的,阻塞后退出await()时也是持有锁的。signal() signalAll()也是一样。
*
* 从实现内部的持有锁情况来看:
*
* await()在从开头到fullyRelease执行前,是持有锁的。
* await()在从fullyRelease执行后 到 acquireQueued执行前,是没有持有锁的。
* await()在 acquireQueued执行后到最后,是持有锁的。
* signal() signalAll()全程都是持有锁的。
* await()的整体流程如下:
*
* 将当前线程包装成一个node后(Node node = addConditionWaiter()),完全释放锁(int savedState = fullyRelease(node))。
* 当前线程阻塞在LockSupport.park(this)处,等待signal线程或者中断线程的到来。
* 被唤醒后,到达acquireQueued之前,当前线程的node已经置于sync queue之上了。
* 执行acquireQueued,进行阻塞式的抢锁。
* 退出acquireQueued时,当前线程已经重新获得了锁,之后进行善后工作。
*/
public final void await() throws InterruptedException {
// 在调用await之前,当前线程就已经被中断了,那么抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程包装进Node,然后放入当前Condition的条件队列
Node node = addConditionWaiter();
// 释放锁,不管当前线程重入锁多少次,都要释放干净
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果当前线程node不在同步队列上,说明还没有别的线程调用 当前Condition的signal。
// 第一次进入该循环,肯定会符合循环条件,然后park阻塞在这里
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// await()第一次调用park之后
// 关于状态不为CONDITION,前面有说过,有两种线程可以使得条件队列上的node的状态从CONDITION变成0。
// 但现在可以排除中断await当前线程的线程这种情况(之后具体介绍这个流程),
// 因为如果发生了中断,await的while循环直接就会break出循环了(if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;),
// 也就不会执行到isOnSyncQueue函数了。
// transferForSignal假设不唤醒node线程
// 按照上面signal的流程(包括signal和signalAll),假设之前node线程没有被中断过,且执行signalAll的线程不唤醒node线程,那么执行signal流程完毕后此时node的状态为:
//
// 在数据结构上,node已经离开了条件队列(first.nextWaiter = null),处于了同步队列上了(Node p = enq(node))。
// 在执行过程上,node线程当前还是阻塞在LockSupport.park(this)这里。(这一点没有变化)
// 要想node线程执行完await()方法,得需要执行unlock的线程出马了。
//
// 当node已经成为了head后继,且获得独占锁的线程开始执行unlock释放锁,将会唤醒node线程。
// node线程从LockSupport.park(this)处唤醒后,不会因为有中断状态而break出循环
// (假设没有被中断过,先不用看checkInterruptWhileWaiting的实现,这里只需要知道interruptMode还是会保持为0就行),
// 然后判断循环条件!isOnSyncQueue(node)发现不成立而退出循环,然后将执行acquireQueued(node, savedState),
// 但也不一定能获得锁,如果不能获得,自然还是阻塞在acquireQueued的shouldParkAfterFailedAcquire里。
// transferForSignal假设唤醒node线程
// 按照上面signal的流程,假设之前node线程没有被中断过,且执行signalAll的线程唤醒node线程,那么执行signal流程完毕后此时node的状态为:
//
// 在数据结构上,node已经离开了条件队列(first.nextWaiter = null),处于了同步队列上了(Node p = enq(node))。
//
// 在执行过程上,node线程从LockSupport.park(this)这里被唤醒,不会因为有中断状态而break出循环,
// 然后判断循环条件!isOnSyncQueue(node)发现不成立而退出循环,然后执行acquireQueued。
// 如果不能获得锁,还是会阻塞在acquireQueued的shouldParkAfterFailedAcquire里。
//
// 要想node线程执行完await()方法,还是得需要执行unlock的线程出马。
// 它执行unlock后,node线程从acquireQueued的shouldParkAfterFailedAcquire处被唤醒,然后再一次去获得锁。
// 但也不一定能获得锁,如果不能获得,自然还是阻塞在acquireQueued的shouldParkAfterFailedAcquire里。
// 中断await当前线程的线程终于出马了(现在考虑没有执行signal的线程,或者说中断这个node在signal这个node之前),看看中断流程是怎样的流程。
// 首先要知道,中断await当前线程的线程执行完中断动作后,我们就不用关心它了,剩余动作还是靠node线程自己完成的。
// 本场景下,中断来临之前,node的状态就和 await()第一次调用park之后 一样:
// 在数据结构上,node在条件队列上(addConditionWaiter)。
// 在执行过程上,node线程当前阻塞在LockSupport.park(this)这里。
// 首先,中断来了以后,node线程会从LockSupport.park(this)处被唤醒,然后执行checkInterruptWhileWaiting
//(之前一直没有讲这个函数,是因为在这个流程中它才会真正发挥作用,之前的signal流程它肯定会返回0的,而返回就不会break出循环)。
// 如果被中断了,跳出等待被signal的循环,并调用transferAfterCancelledWait,节点从condition队列放到同步队列,跳出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 既然已经执行到了acquireQueued,说明又会走独占锁的获取过程了,在此不赘述了。我们只需要知道,
// 从acquireQueued返回时,node线程已经获取到了锁,并且返回了acquireQueued过程中是否有过中断。
// 注意,这和acquireQueued执行前发生的中断是两个不同的中断,
// acquireQueued执行前发生的中断会被checkInterruptWhileWaiting消耗掉,并赋值给interruptMode的。
// 注意,int savedState = fullyRelease(node)之前释放的同步器状态,现在acquireQueued(node, savedState)都要重新全部再获取回来。
// acquireQueued期间发生的中断,重要性不如acquireQueued之前发生的中断。
// 假设acquireQueued发生了中断,acquireQueued(node, savedState)则返回true,然后此时interruptMode有三种情况:
// interruptMode为0,说明acquireQueued之前没发生过中断。
// interruptMode != THROW_IE判断成功。所以需要将interruptMode升级为REINTERRUPT。
// interruptMode为REINTERRUPT,说明acquireQueued之前发生过中断(signal流程在中断流程之前的那种)。
// interruptMode != THROW_IE判断成功。然后将interruptMode从REINTERRUPT变成REINTERRUPT,这好像是脱裤子放屁,但逻辑这样写就简洁了。
// interruptMode为THROW_IE,说明acquireQueued之前发生过中断(中断流程在signal流程之前的那种)。
// interruptMode != THROW_IE判断失败。不会去执行interruptMode = REINTERRUPT,因为执行了反而使得中断等级下降了。
// 说到底,还是因为acquireQueued期间发生的中断,重要性不如acquireQueued之前发生的中断。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果发现node的条件队列的nextWaiter还没有断开,则需要做一下善后处理。回想signal流程和中断流程:
// signal流程中(signal和signalAll方法),都会执行first.nextWaiter = null;的,所以如果node线程之前走的是signal流程,那这里不会执行。
// 中断流程中,不会去执行first.nextWaiter = null;的,所以如果node线程之前走的是中断流程,那这里会执行。
if (node.nextWaiter != null) // 如果被取消,进行清理,transferAfterCancelledWait没有清理nextWaiter
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
/**
* 实现有时间限制的条件等待。
* <ol>
* <li> 如果当前线程被中断,则抛出InterruptedException。
* <li> 保存getState返回的锁状态。
* <li> 以保存的状态作为参数调用release,如果失败则抛出IllegalMonitorStateException。
* <li> 阻塞直到被signal或者被中断或者时间到了
* <li> 通过调用特定版本的acquire,并将保存的状态作为参数进行重新获取。
* <li> 如果在步骤4中阻塞时被中断,则抛出InterruptedException。
* </ol>
*
* 前面的方法不管是await和awaitUninterruptibly,它们在while循环中如果一直没有中断线程或者signal线程的到来,
* 会一直阻塞在while循环的park处。如果长时间signal线程一直不来,当前线程就会一直阻塞
* (一直阻塞就会一直不会去执行acquireQueued,也就不可能执行完函数了),
* 所以此时我们可能需要一个带有超时机制的awaitNanos(long nanosTimeout),
* 如果超时了就啥也不用管,直接去执行acquireQueued。
*
* 参数nanosTimeout,代表你最多愿意在这个方法等待多长时间。
* 返回值long,代表(nanostimeout值 减去 花费在等待在此方法上的时间 )的估算。
*
* 总结一下awaitNanos(long nanosTimeout):相比await()方法,它能在超时后,无条件地去执行acquireQueued,而这不需要signal线程或中断线程的到来。
*/
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
// 需要计算出一个deadline,作为是否超时的标准。
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 如果超出了时间限制,节点状态从condition变成0,节点加入同步节点
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
// 如果时间还够,线程阻塞
if (nanosTimeout >= spinForTimeoutThreshold)
// 如果LockSupport.parkNanos(this, nanosTimeout)之后的这段长达nanosTimeout的时间段内,既没有中断来临
//(不会进入if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)分支而break出循环),也没有signal来临
//(那么当前线程node还是不处于sync queue上,循环条件!isOnSyncQueue(node)通过),线程足足阻塞了nanosTimeout这么久才被唤醒,
// 那么经过nanosTimeout = deadline - System.nanoTime()重新计算后,就肯定会进入到if (nanosTimeout <= 0L)分支,
// 执行transferAfterCancelledWait将node入队同步队列,然后退出循环,开开心心地去执行acquireQueued就好了。
LockSupport.parkNanos(this, nanosTimeout);
// 只有当nanosTimeout >= spinForTimeoutThreshold,才可以阻塞当前线程,不然时间太短的话,就直接自旋就好了。
// 这是因为考虑到阻塞线程和唤醒线程的过程,时间太短就不好控制了。注意进入这个分支的可能性:
// 用户给的nanosTimeout太小,第一次进入循环时,就开始自旋。
// 走了signal流程,signal流程在一定条件下唤醒了当前线程。但唤醒时剩余时间已经很少了。
// 走了signal流程,但没有唤醒当前线程。之后当前线程node已经成为了head后继,然后另一个线程执行了unlock,唤醒了当前线程。但唤醒时剩余时间已经很少了。
// 不可能是走的中断流程。因为会直接break出循环,也就不会执行这个分支。
// 如果被中断,节点状态从condition变成0,节点加入同步节点
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
// nanosTimeout = deadline - System.nanoTime()计算出剩余时间还有多久。能执行到这里,说明之前肯定没有过中断。
nanosTimeout = deadline - System.nanoTime();
}
// 我们来总结下awaitNanos(long nanosTimeout)中能执行到acquireQueued的几种流程:
// 都已经超时了,且之前没有过中断,那么接下来就会去执行acquireQueued,不过分两种情况:
// 如果之前signal线程来过,signal线程就已经把当前线程node放到同步队列里去了,所以!isOnSyncQueue(node)循环条件不成立,直接退出循环进而去执行的acquireQueued。
// 如果之前signal线程没有来过,!isOnSyncQueue(node)循环条件成立,
// 进入if (nanosTimeout <= 0L)分支去执行transferAfterCancelledWait让当前线程node先入队后,再去执行acquireQueued。
// 因为来了中断,而去执行的acquireQueued。这个过程和await()一样。
// 因为signal流程中的一定条件的唤醒,或因为执行unlock的线程而唤醒。
// 这两种情况,当前线程node都已经处于同步队列上了,所以循环条件不成立而退出循环,进而去执行的acquireQueued。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
// 如果被取消,进行清理,transferAfterCancelledWait没有清理nextWaiter
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
// return deadline - System.nanoTime()返回剩余时间还有多久。
return deadline - System.nanoTime();
}
/**
* Implements absolute timed condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled, interrupted, or timed out.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* <li> If timed out while blocked in step 4, return false, else true.
* </ol>
*
* 这个方法其实和await(long time, TimeUnit unit)几乎一模一样,只是获得deadline的方式改变了而已,以前是自己计算出来。
*
* 分析一下不同之处:
* long abstime = deadline.getTime(),直接获得这个绝对时间。
* 此处不需要计算出deadline了,因为参数给了。
* 以前是判断 deadline与当前系统时间 之间的差值。现在是比较 deadline与当前系统时间 之间的大小。
* 相比之前两个超时版本,这里没有使用自旋优化,在剩余时间特别短的时候。所以调用这个方法时,最好给定的绝对时间比较远,才比较好。
*/
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
/**
* Implements timed condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled, interrupted, or timed out.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* <li> If timed out while blocked in step 4, return false, else true.
* </ol>
*
* 理解了awaitNanos(long nanosTimeout),这个await(long time, TimeUnit unit)方法就好懂多了。
* 从参数上就可以看出来,它只是对时间单位进行了拓展。我们直接看看它与awaitNanos(long nanosTimeout)的不同之处。
*
* 可以发现,await(long time, TimeUnit unit)的整个过程与awaitNanos(long nanosTimeout)几乎一样,只是返回值类型不一样了。
* 但await(long time, TimeUnit unit)只关心退出while循环的原因,awaitNanos(long nanosTimeout)关心的是整个执行过程中花费的时间。
*
* 所以,并不能简单的认为,调用await(long time, TimeUnit unit) 等价于 调用awaitNanos(unit.toNanos(time)) > 0。
* 比如考虑这种场景,假设没有中断,退出循环是因为signal线程来过才退出的循环,但直到执行unlock的线程来唤醒当前线程进而使得当前线程得到锁却很迟。此时:
*
* awaitNanos(unit.toNanos(time))返回的值是小于0的,所以awaitNanos(unit.toNanos(time)) > 0返回false。
* await(long time, TimeUnit unit)退出while循环之前,不会去执行timedout = transferAfterCancelledWait(node),
* 因为是直接不满足了循环条件!isOnSyncQueue(node)。所以timedout = false,返回!timedout,即返回的是true了。
* 按照上面两点,发现二者不一致了。
*
*/
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
// 将时间最终都转换为nanos的时间。
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
// 增加了一个局部变量timedout,代表退出while循环是否是因为超时才退出的。退出循环后会接着执行acquireQueued。
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 如果当前线程因为超时而唤醒,会进入到if (nanosTimeout <= 0L)分支,之后如果transferAfterCancelledWait执行成功,
// 会将局部变量timedout置为true,代表退出while循环是因为超时才退出的。
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
// 返回值是boolean型。
// 返回!timedout,所以返回值为false,代表退出while循环是因为超时才退出的;
// 返回值为true,代表退出while循环不是因为超时才退出的。
return !timedout;
}
// 支持工具
/**
* 如果此条件是由给定的同步对象创建的,则返回true。
*
* @return {@code true} if owned
*/
final boolean isOwnedBy(AbstractQueuedSynchronizer sync) {
return sync == AbstractQueuedSynchronizer.this;
}
/**
* 查询是否有线程正在等待此条件。
* 实现AbstractQueuedSynchronizer.hasWaiters (ConditionObject)。
*
* @return {@code true} if there are any waiting threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
protected final boolean hasWaiters() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
return true;
}
return false;
}
/**
* 返回等待此条件的线程数的估计数。
* 实现AbstractQueuedSynchronizer.getWaitQueueLength (ConditionObject)。
*
* @return the estimated number of waiting threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
protected final int getWaitQueueLength() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int n = 0;
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
++n;
}
return n;
}
/**
* 返回一个集合,其中包含可能正在等待此条件的线程。
* 实现AbstractQueuedSynchronizer.getWaitingThreads (ConditionObject)。
*
* @return the collection of threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
protected final Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION) {
Thread t = w.thread;
if (t != null)
list.add(t);
}
}
return list;
}
}
字段unsafe,5个XXXOffset,4个compareAndSetXXX
/**
* 设置以支持compareAndSet。
* 我们需要在这里本机实现这个:为了允许未来的增强,我们不能显式地子类化AtomicInteger,否则这将是高效和有用的。
* 因此,为了解决这个问题,我们使用了hotspot intrinsics API。
* 当我们这样做的时候,我们对其他CASable字段做同样的事情(否则可以用原子字段更新器来做)。
*
* 需要通过CAS操作来修改的属性有AQS的state、head和tail;Node的waitStatus和next。因为这几个属性确实会被多线程访问。
* 最后,我们利用Unsafe的compareAndSetXXX即CAS方法。
*/
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
/**
* 仅仅被enq方法使用,CAS修改head字段
*/
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
/**
* 仅仅被enq方法使用,CAS修改tail字段
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
/**
* CAS修改一个节点的waitStatus字段
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
/**
* CAS修改一个节点的next字段
*/
private static final boolean compareAndSetNext(Node node,
Node expect,
Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}