同步器依赖于内部的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并发编程的艺术》