Inside AbstractQueuedSynchronizer (2)

3 AbstractQueuedSynchronizer java

 

3.1 Inheritance node

    AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer。AbstractOwnableSynchronizer继承自Object而且实现了Serializable接口,它只有一个成员变量private transient Thread exclusiveOwnerThread以及对应的getter/setter方法。该成员变量用于保存当前拥有排他访问权的线程。须要注意的是,该成员变量没有用volatile关键字修饰。 并发


3.2 State ide

    AbstractQueuedSynchronizer用一个int(private volatile int state)来保存同步状态,以及对应的getter/setter/compareAndSetState方法。Java6新增了一个AbstractQueuedLongSynchronizer,它用一个long来保存同步状态,貌似目前没有被java.util.concurrent中的其它synchronizer所使用。对于ReentrantLock,state为0则意味着锁没有被任何线程持有;不然state保存了持有锁的线程的重入次数。 oop

 

3.3 WaitQueue 优化

    WaitQueue是AbstractQueuedSynchronizer的核心,它用于保存被阻塞的线程。它的实现是"CLH" (Craig, Landin, and Hagersten) lock queue的一个变种。 this

 

    标准的CLH lock queue一般被用来实现spin lock,它经过TheadLocal变量pred引用队列中的前一个节点(Node自己没有指向先后节点的引用),如下是标准的CLH lock queue的一个参考实现: spa

Java代码   收藏代码
  1. public class ClhSpinLock {  
  2.     private final ThreadLocal<Node> pred;  
  3.     private final ThreadLocal<Node> node;  
  4.     private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());  
  5.   
  6.     public ClhSpinLock() {  
  7.         this.node = new ThreadLocal<Node>() {  
  8.             protected Node initialValue() {  
  9.                 return new Node();  
  10.             }  
  11.         };  
  12.   
  13.         this.pred = new ThreadLocal<Node>() {  
  14.             protected Node initialValue() {  
  15.                 return null;  
  16.             }  
  17.         };  
  18.     }  
  19.   
  20.     public void lock() {  
  21.         final Node node = this.node.get();  
  22.         node.locked = true;  
  23.         Node pred = this.tail.getAndSet(node);  
  24.         this.pred.set(pred);  
  25.         while (pred.locked) {}  
  26.     }  
  27.   
  28.     public void unlock() {  
  29.         final Node node = this.node.get();  
  30.         node.locked = false;  
  31.         this.node.set(this.pred.get());  
  32.     }  
  33.   
  34.     private static class Node {  
  35.         private volatile boolean locked;  
  36.     }  
  37. }  

    其逻辑并不复杂:对于lock操做,只须要经过一个CAS操做便可将当前线程对应的节点加入到队列中,而且同时得到了predecessor节点的引用,而后就是等待predecessor释放锁;对于unlock操做,只须要将当前线程对应节点的locked成员变量设置为false。unlock方法中的this.node.set(this.pred.get())主要目的是重用predecessor上的Node对象,这是对GC友好的一个优化。若是不考虑这个优化,那么this.node.set(new Node())也是能够的。跟那些TAS(test and set) spin lock和TTAS(test test and set) spin lock相比,CLH spin lock主要是解决了cache-coherence traffic的问题:每一个线程在busy loop的时候,并无竞争同一个状态,而是只判断其对应predecessor的锁定状态。若是你担忧false sharing问题,那么能够考虑将锁定状态padding到cache line的长度。此外,CLH spin lock经过FIFO的队列保证了锁竞争的公平性。 .net

 

    AbstractQueuedSynchronizer的静态内部类Node维护了一个FIFO的等待队列。跟CLH不一样的是,Node中包含了指向predecessor和sucessor的引用。predecessor引用的做用是为了支持锁等待超时(timeout)和锁等待回退(cancellation)的功能。sucessor的做用是为了支持线程阻塞:在Inside AbstractQueuedSynchronizer (1) 中提到过,AbstractQueuedSynchronizer经过LockSupport实现了线程的block/unblock,所以须要经过successor引用找到后续的线程并将其唤醒(CLH spin lock由于是spin,因此不须要显式地唤醒)。此外,Node中还包括一个volatile int waitStatus成员变量用于控制线程的阻塞/唤醒,以及避免没必要要的调用LockSupport的park/unpark方法。须要注意的是,虽然AbstractQueuedSynchronizer在绝大多数状况下是经过LockSupport进行线程的阻塞/唤醒,可是在特定状况下也会使用spin lock,static final long spinForTimeoutThreshold = 1000L这个静态变量设定了使用spin lock的一个阈值。 线程

 

    对WaitQueue进行enqueue操做相关的代码以下:

Java代码   收藏代码
  1. private Node addWaiter(Node mode) {  
  2.     Node node = new Node(Thread.currentThread(), mode);  
  3.     // Try the fast path of enq; backup to full enq on failure  
  4.     Node pred = tail;  
  5.     if (pred != null) {  
  6.         node.prev = pred;  
  7.         if (compareAndSetTail(pred, node)) {  
  8.             pred.next = node;  
  9.             return node;  
  10.         }  
  11.     }  
  12.     enq(node);  
  13.     return node;  
  14. }  
  15.   
  16. private Node enq(final Node node) {  
  17.     for (;;) {  
  18.         Node t = tail;  
  19.         if (t == null) { // Must initialize  
  20.             if (compareAndSetHead(new Node()))  
  21.                 tail = head;  
  22.         } else {  
  23.             node.prev = t;  
  24.             if (compareAndSetTail(t, node)) {  
  25.                 t.next = node;  
  26.                 return t;  
  27.             }  
  28.         }  
  29.     }  
  30. }  

    addWaiter方法中的那个if分支,实际上是一种优化,若是失败那么会调用enq方法进行enqueue。须要注意的是,以上代码中对next引用的设定是在enqueue成功以后进行的。这样作虽然没有并发问题,可是在判断一个node是否有sucessor时,不能仅仅经过next == null来判断,由于enqueue和设置next引用这两个步骤不是一个原子操做。

 

    对WaitQueue进行dequeue操做相关的代码以下:

Java代码   收藏代码
  1. private void setHead(Node node) {  
  2.     head = node;  
  3.     node.thread = null;  
  4.     node.prev = null;  
  5. }  

    setHead方法没有用到锁,也没有使用CAS,这样没有并发问题?没有,由于这个方法只会被持有锁的线程所调用,此时只须要将head指向持有锁的线程对应的node便可。

相关文章
相关标签/搜索