分享

Inside AbstractQueuedSynchronizer (3) (此版比较完整)

 hh3755 2014-08-13

Inside AbstractQueuedSynchronizer (1)

Inside AbstractQueuedSynchronizer (2)

Inside AbstractQueuedSynchronizer (3)

Inside AbstractQueuedSynchronizer (4)

 

3.4 Template Method

    AbstractQueuedSynchronizer提供了以下几个protected方法用于子类改写

Java代码  收藏代码
  1. protected boolean tryAcquire(int arg)  
  2. protected boolean tryRelease(int arg)  
  3. protected int tryAcquireShared(int arg)  
  4. protected boolean tryReleaseShared(int arg)  
  5. protected boolean isHeldExclusively()  

    这几个方法的默认实现是抛出UnsupportedOperationException,子类可以根据需要进行改写。

 

    AbstractQueuedSynchronizer中最基本的acquire流程的相关代码如下:

Java代码  收藏代码
  1. public final void acquire(int arg) {  
  2.     if (!tryAcquire(arg) &&  
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.         selfInterrupt();  
  5. }  
  6.   
  7. final boolean acquireQueued(final Node node, int arg) {  
  8.     boolean failed = true;  
  9.     try {  
  10.         boolean interrupted = false;  
  11.         for (;;) {  
  12.             final Node p = node.predecessor();  
  13.             if (p == head && tryAcquire(arg)) {  
  14.                 setHead(node);  
  15.                 p.next = null// help GC  
  16.                 failed = false;  
  17.                 return interrupted;  
  18.             }  
  19.             if (shouldParkAfterFailedAcquire(p, node) &&  
  20.                 parkAndCheckInterrupt())  
  21.                 interrupted = true;  
  22.         }  
  23.     } finally {  
  24.         if (failed)  
  25.             cancelAcquire(node);  
  26.     }  
  27. }  

    如果tryAcquire失败,那么当前线程可能会被enqueue到WaitQueue,然后被阻塞。 shouldParkAfterFailedAcquire方法会确保每个线程在被阻塞之前,其对应WaitQueue中的节点的waitStatus被设置为Node.SIGNAL(-1),以便在release时避免不必要的unpark操作。此外shouldParkAfterFailedAcquire还会清理WaitQueue中已经超时或者取消的Node。需要注意的是,在某个线程最终被阻塞之前,tryAcquire可能会被多次调用。

 

    AbstractQueuedSynchronizer中最基本的release流程的相关代码如下:

Java代码  收藏代码
  1. public final boolean release(int arg) {  
  2.     if (tryRelease(arg)) {  
  3.         Node h = head;  
  4.         if (h != null && h.waitStatus != 0)  
  5.             unparkSuccessor(h);  
  6.         return true;  
  7.     }  
  8.     return false;  
  9. }  
  10.   
  11. private void unparkSuccessor(Node node) {  
  12.     /* 
  13.      * If status is negative (i.e., possibly needing signal) try 
  14.      * to clear in anticipation of signalling.  It is OK if this 
  15.      * fails or if status is changed by waiting thread. 
  16.      */  
  17.     int ws = node.waitStatus;  
  18.     if (ws < 0)  
  19.         compareAndSetWaitStatus(node, ws, 0);  
  20.   
  21.     /* 
  22.      * Thread to unpark is held in successor, which is normally 
  23.      * just the next node.  But if cancelled or apparently null, 
  24.      * traverse backwards from tail to find the actual 
  25.      * non-cancelled successor. 
  26.      */  
  27.     Node s = node.next;  
  28.     if (s == null || s.waitStatus > 0) {  
  29.         s = null;  
  30.         for (Node t = tail; t != null && t != node; t = t.prev)  
  31.             if (t.waitStatus <= 0)  
  32.                 s = t;  
  33.     }  
  34.     if (s != null)  
  35.         LockSupport.unpark(s.thread);  
  36. }  

    release方法中,总是总head节点开始向后查找sucessor。只有当该sucessor的waitStatus被设置的情况下才会调用unparkSuccessor。unparkSuccessor方法中首先清除之前设置的Node.waitStatus,然后向后查找并且唤醒第一个需要被唤醒的sucessor。需要注意的是,if (s == null || s.waitStatus > 0)这个分支中,查找是从tail节点开始,根据prev引用向前进行。在Inside AbstractQueuedSynchronizer (2)   中提到过,Node.next为null并不一定意味着没有sucessor,虽然WaitQueue是个双向链表,但是根据next引用向后查找sucessor不靠谱,而根据prev引用向前查找predecessor总是靠谱。

 

3.5 Fairness

    到目前为止我们已经知道,WaitQueue是个FIFO的队列,唤醒也总是从head开始。但是AbstractQueuedSynchronizer却并不一定是公平的(实际上,大多数情况下都是在非公平模式下工作)。如果在看一遍acquire方法会发现,tryAcquire的调用顺序先于acquireQueued,也就是说后来的线程可能在等待中的线程之前acquire成功。这种场景被称为barging FIFO strategy,它能提供更高的吞吐量。

 

    大多数AbstractQueuedSynchronizer的子类都同时提供了公平和非公平的实现,例如ReentrantLock提供了NonfairSync和FairSync。例如其FairSync的tryAcquire方法如下:

Java代码  收藏代码
  1. protected final boolean tryAcquire(int acquires) {  
  2.     final Thread current = Thread.currentThread();  
  3.     int c = getState();  
  4.     if (c == 0) {  
  5.         if (!hasQueuedPredecessors() &&  
  6.             compareAndSetState(0, acquires)) {  
  7.             setExclusiveOwnerThread(current);  
  8.             return true;  
  9.         }  
  10.     }  
  11.     else if (current == getExclusiveOwnerThread()) {  
  12.         int nextc = c + acquires;  
  13.         if (nextc < 0)  
  14.             throw new Error("Maximum lock count exceeded");  
  15.         setState(nextc);  
  16.         return true;  
  17.     }  
  18.     return false;  
  19. }  

   tryAcquire方法返回true的条件之一是!hasQueuedPredecessors() 。hasQueuedPredecessors的代码如下:

Java代码  收藏代码
  1. public final boolean hasQueuedPredecessors() {  
  2.     // The correctness of this depends on head being initialized  
  3.     // before tail and on head.next being accurate if the current  
  4.     // thread is first in queue.  
  5.     Node t = tail; // Read fields in reverse initialization order  
  6.     Node h = head;  
  7.     Node s;  
  8.     return h != t &&  
  9.         ((s = h.next) == null || s.thread != Thread.currentThread());  
  10. }  

    综上, FairSync优先确保等待中线程先acquire成功。但是公平性也不是绝对的:在一个多线程并发的环境下,就算锁的获取是公平的,也不保证后续的其它处理过程的先后顺序。

 

    既然默认情况下使用的都是NonfairSync,那么FairSync适合什么样的场景呢?如果被锁所保护的代码段的执行时间比较长,而应用又不能接受线程饥饿(NonfairSync可能会导致虽然某个线程长时间排队,但是仍然无法获得锁的情况)的场景下可以考虑使用FairSync。对于ReentrantLock,在其构造函数中传入true,即可构造一把公平锁。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多