队列结点
Node类型的waitStatus、prev、next 字段都用volatile 修饰,这样直接的读写操作就具有内存可视性。表示Node状态的waitStatus字段是个int类型,这样通过数值比较就可以判断Node的状态,而不需要很多的分支语句。
它的构造函数也是比较有意思的,有三个,分别用于构建同步队列的初始头结点或共享标识、构造同步队列的有效结点、构造条件队列的结点。也就是说,同步队列和条件队列的结点是相同的类型,所以可以从条件队列转移到同步队列去获取许可。
static final class Node {
// 表明节点是否以共享模式等待的标记
static final Node SHARED = new Node();
// 表明节点是否以独占模式等待的标记
static final Node EXCLUSIVE = null;
// 表明线程已被取消
static final int CANCELLED = 1;
// 表明后续节点的线程需要unparking
static final int SIGNAL = -1;
// 表明线程正在等待一个条件
static final int CONDITION = -2;
// 表明下一次acquireShared应该无条件传播
static final int PROPAGATE = -3;
/*
* 状态字段,只能取下面的值:
* SIGNAL(-1): 这个结点的后继是(或很快是)阻塞的(通过park),所以当前结点
* 必须unpark它的后继,当它释放或取消时。为了避免竞争,acquire方法必须
* 首先表明它们需要一个信号,然后再次尝试原子性acquire,如果失败了就阻塞。
*
* CANCELLED(1): 这个结点由于超时或中断已被取消。结点从不离开这种状态。尤其是,
* 这种状态的线程从不再次阻塞。
*
* CONDITION(-2): 这个结点当前在一个条件队列上。它将不会用于sync队列的结点,
* 直到被转移,在那时,结点的状态将被设为0.
* 这个值在这里的使用与其他字段的使用没有关系,仅仅是简化结构。
*
* PROPAGATE(-3): releaseShared应该传递给其他结点。这是在doReleaseShared里设置
* (仅仅是头结点)以确保传递继续,即使其他操作有干涉。
*
* 0: 非以上任何值。
*
* 值是组织为数字的用以简化使用。非负值表示结点不需要信号。这样,大部分代码不需要
* 检查特定的值,只需要(检查)符号。
*
* 对于普通同步结点,字段初始化为0;对于条件结点初始化为CONDITION(-2)。
* 通过CAS操作修改(或者,当允许时,用无条件volatile写。)
*/
volatile int waitStatus;
/*
* 连接到当前结点/线程依赖的用来检查等待状态的前驱结点。
* 在进入队列时赋值,只在出队列时置为空(为了GC考虑)。
* 根据前驱结点的取消,我们使查找一个非取消结点的while循环短路,这个总是会退出,
* 因为头结点从不会是取消了的:一个结点成为头只能是一次成功的acquire操作结果。
*
* 一个取消了的线程从不会在获取操作成功,线程只能取消自己,不能是其他结点。
*/
volatile Node prev;
/*
* 连接到当前结点/线程释放时解除阻塞的后续结点。
* 在入队列时赋值,在绕过已取消前驱节点时调整,出队列时置为空(for GC)。
* 入队操作不会给前驱结点的next字段赋值,直到附件后(把新节点赋值给队列的tail属性?),
* 所以看到next字段为空不一定表示它就是队列的尾结点。然而,如果next字段看起来是空,
* 我们可以从tail向前遍历进行双重检查。
* 被取消了的结点的next字段被设置为指向它自己而不是空,这让isOnSyncQueue变得容易。
*/
volatile Node next;
/*
* 列队在这个结点的线程,在构造时初始化,用完后置空。
*/
volatile Thread thread;
/*
* 连接到下一个在条件上等待的结点或是特殊的值SHARED。
* 因为条件队列只在独占模式下持有时访问,我们只需要一个简单的链表队列来持有在条件上等待的结点。
* 他们然后被转移到队列去re-acquire。
* 因为条件只能是独占的,我们通过用一个特殊的值来表明共享模式 来节省一个字段。
*/
Node nextWaiter;
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
添加结点到等待队列
对于添加结点到队列的操作最重要的是要保证:即使添加的CAS操作失败了,也不能影响队列结点现有的连接关系。
对于新结点,它在CAS之前指向它的预期前驱,CAS成功之后再更新预期前驱的后继指针。
在步骤1成功之后、步骤2完成之前,其他线程通过结点的 “next” 连接可能看到“尾结点”(即代码里的 pred)的 “next” 为空,但其实队列里已经加入新的结点,这也是为什么通过 “next” 连接遍历队列时碰到后继为空的,必须从原子地更新的 “tail” 结点向后遍历。
继续阅读