同步器依赖于内部的FIFO双向等待队列来完成同步状态的管理,该等待队列是CLH队列的变种,CLH队列通常用于自旋锁,同步器中的等待队列可以简单的理解为“等待锁的线程队列”。当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点,Node类的定义如下:
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the 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() { // 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;
}
}
节点的属性类型与名称以及描述如下表所示:
属性类型与名称 | 描 述 |
Node SHARED | 表示节点使用共享模式等待 |
Node EXCLUSIVE | 表示节点使用独占模式等待 |
int waitStatus | 等待状态,包含如下几个状态: 1、CANCELLED,值为1,由于在同步队列中等待的线程等待超时或者被中断,该节点不会参与 同步状态的竞争,需要从同步队列中取消等待,节点进入该状态后将不会再变化; 2、SIGNAL,值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态 或者被取消,将会通知后继节点,使后继节点的线程得以运行; 3、CONDITION,值为-2,节点在等待队列中,节点线程等待在Condition上,当其他线程对 Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到同步状态 的获取中; 4、PROPAGATE,值为-3,表示下一次共享式同步状态获取将会无条件地传播下去; 5、初始值为0 |
Node prev | 前驱节点,当节点加入同步队列时被设置(尾部添加) |
Node next | 后继节点 |
Node nextWaiter | 等待队列中的后继节点。如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型 (独占和共享)和等待队列中的后继节点公用同一个字段 |
Thread thread | 获取同步状态的线程 |
在上图中,同步器包含了两个节点引用类型,一个指向头结点,另一个指向尾节点。
入队
获取同步状态失败的线程要被构造成节点,并被加入到同步队列尾部,而这个加入队列的过程必须要保证线程安全,因此,同步器提供了一个基于CAS的设置尾节点的方法:
private final boolean compareAndSetTail(Node expect, Node update)
只有当设置成功后,当前节点才正式与之前的尾节点建立关联。添加节点的操作是通过addWaiter(Node)方法完成的,源码如下:
// 为当前线程创建一个节点并入队,然后返回这个节点
// 使用CAS算法入队并设置为尾节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 如果队尾不为null,则尝试插入队列
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果队尾为null,则调用enq(Node)方法插入
enq(node);
return node;
}
addWaiter(Node)方法主要是将当前线程构造为一个Node节点,然后入队,入队时,首先尝试的是快速入队。何为快速入队?直接把我们刚才构造的Node的前驱指针指向当前尾节点,然后通过CAS操作把我们刚才构造的node作为新的尾节点,最后再把原来老的尾节点的后继指针指向现在的新的尾节点。
快速入队的前提是这个同步队列必须先存在。如果不存在,那么只能走常规的入队操作流程,也就是进入到enq(Node)方法中。从这里我们也可以知道,其实队列的初始化在同步器的整个生命周期中只会执行一次,后续的入队操作都会按快速入队的方式入队。
enq(Node)方法源码如下:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 队列必须初始化,若多个线程并发执行此操作,通过CAS能保证只有一个线程执行成功
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 采用快速入队的方式入队
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq(Node)方法使用死循环以及CAS的方式来保证节点正确添加到同步队列中。同步器将节点加入到同步队列的过程如下图所示:
出队
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头结点的方法不需要使用CAS操作来保证线程安全,它只需要将首节点设置成原首节点的后继节点并断开原首节点的next引用即可。设置首节点的操作是通过setHead(Node)方法来完成的,源码如下:
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
设置首节点的过程如下图所示:
相关博客
AbstractQueuedSynchronizer独占式同步状态获取与释放
AbstractQueuedSynchronizer共享式同步状态获取与释放
参考资料
方腾飞:《Java并发编程的艺术》