AbstractQueuedSynchronizer/AQS 使用拓展分析-優

 

java.util.concurrent.locks.AbstractQueuedSynchronizer是J.U.C裏最核心,也是最複雜的一個基礎的類,簡稱AQS。AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基礎,因此AbstractQueuedSynchronizer是Lock/Executor實現的前提。

通過以下三個維度來實現:

第一個維度:原子性操作同步器的狀態位

這裏使用一個32位的整數來描述狀態位,(AbstractQueuedSynchronizer類中有個屬性叫:private volatile int state;
),在這裏依然使用CAS操作來解決這個問題。事實上這裏還有一個64位版本的同步器(AbstractQueuedLongSynchronizer,裏面是long state

第二個維度:阻塞和喚醒線程

JDK1.5之前使用Thread.suspend和Thread.resume實現,屬於過時的api了,也有可能發生死鎖。Jdk1.5之後使用LockSupport類來實現。AQS: AbstractQueuedSynchronizer,就是通過調用 LockSupport .park() LockSupport .unpark()實現線程的阻塞和喚醒 的。提供的方法比如:

LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object,long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object,long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)

LockSupportJDK中比較底層的類,是用來創建鎖和其他同步工具類的基本線程阻塞原語。LockSupport 很類似於二元信號量(只有1個許可證可供使用),如果這個許可還沒有被佔用,當前線程獲取許可並繼  執行;如果許可已經被佔用,當前線 程阻塞,等待獲取許可。 

public static void main(String[] args)
{
     LockSupport.park();
     System.out.println("block.");
}

運行該代碼,可以發現主線程一直處於阻塞狀態。因爲 許可默認是被佔用的 ,調用park()時獲取不到許可,所以進入阻塞狀態。

如下代碼:先釋放許可,再獲取許可,主線程能夠正常終止。LockSupport許可的獲取和釋放,一般來說是對應的,如果多次unpark,只有一次park也不會出現什麼問題,結果是許可處於可用狀態。

public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//釋放許可
     LockSupport.park();// 獲取許可
     System.out.println("b");
}

LockSupport是可不重入 的,如果一個線程連續2次調用 LockSupport .park(),那麼該線程一定會一直阻塞下去。

public static void main(String[] args) throws Exception
{
  Thread thread = Thread.currentThread();
  
  LockSupport.unpark(thread);
  
  System.out.println("a");
  LockSupport.park();
  System.out.println("b");
  LockSupport.park();
  System.out.println("c");
}

這段代碼打印出ab,不會打印c,因爲第二次調用park的時候,線程無法獲取許可出現死鎖。

LockSupport可以響應中斷性

public static void t2() throws Exception
{
  Thread t = new Thread(new Runnable()
  {
    private int count = 0;
 
    @Override
    public void run()
    {
      long start = System.currentTimeMillis();
      long end = 0;
      while ((end - start) <= 1000)
      {
        count++;
        end = System.currentTimeMillis();
      }
      System.out.println("after 1 second.count=" + count);
    //等待或許許可
      LockSupport.park();
      System.out.println("thread over." + Thread.currentThread().isInterrupted());
    }
  });
 
  t.start();
  Thread.sleep(2000);
  // 中斷線程
  t.interrupt();
  System.out.println("main over");
}

最終線程會打印出thread over.true。這說明 線程如果因爲調用park而阻塞的話,能夠響應中斷請求(中斷狀態被設置成true),但是不會拋出InterruptedException 

 

第三個維度:一個有序的隊列

在AQS中採用CHL列表來解決有序的隊列的問題。

AQS採用的CHL模型採用下面的算法完成FIFO的入隊列和出隊列過程。

對於入隊列(enqueue):採用CAS操作,每次比較尾結點是否一致,然後插入的到尾結點中。

do {

        pred = tail;

}while ( !compareAndSet(pred,tail,node) );

對於出隊列(dequeue):由於每一個節點也緩存了一個狀態,決定是否出隊列,因此當不滿足條件時就需要自旋等待,一旦滿足條件就將頭結點設置爲下一個節點。

while (pred.status != RELEASED) ;

head  = node;

實際上這裏自旋等待也是使用LockSupport.park()來實現的。

AQS裏面有三個核心字段:

private volatile int state;

private transient volatile Node head;

private transient volatile Node tail;

其中state描述的有多少個線程取得了鎖,對於互斥鎖來說state<=1。head/tail加上CAS操作就構成了一個CHL的FIFO隊列。下面是Node節點的屬性。

volatile int waitStatus; 節點的等待狀態,一個節點可能位於以下幾種狀態:

·        CANCELLED = 1: 節點操作因爲超時或者對應的線程被interrupt。節點不應該留在此狀態,一旦達到此狀態將從CHL隊列中踢出。

·        SIGNAL = -1: 節點的繼任節點是(或者將要成爲)BLOCKED狀態(例如通過LockSupport.park()操作),因此一個節點一旦被釋放(解鎖)或者取消就需要喚醒(LockSupport.unpack())它的繼任節點。

·        CONDITION = -2:表明節點對應的線程因爲不滿足一個條件(Condition)而被阻塞。

·        0: 正常狀態,新生的非CONDITION節點都是此狀態。

·        非負值標識節點不需要被通知(喚醒)。

volatile Node prev;此節點的前一個節點。節點的waitStatus依賴於前一個節點的狀態。

volatile Node next;此節點的後一個節點。後一個節點是否被喚醒(uppark())依賴於當前節點是否被釋放。

volatile Thread thread;節點綁定的線程。

Node nextWaiter;下一個等待條件(Condition)的節點,由於Condition是獨佔模式,因此這裏有一個簡單的隊列來描述Condition上的線程節點。

CLH算法實現

CLH隊列中的結點QNode中含有一個locked字段,該字段若爲true表示該線程需要獲取鎖,且不釋放鎖,爲false表示線程釋放了鎖。結點之間是通過隱形的鏈表相連,之所以叫隱形的鏈表是因爲這些結點之間沒有明顯的next指針,而是通過myPred所指向的結點的變化情況來影響myNode的行爲。CLHLock上還有一個尾指針,始終指向隊列的最後一個結點。CLHLock的類圖如下所示:

 

當一個線程需要獲取鎖時,會創建一個新的QNode,將其中的locked設置爲true表示需要獲取鎖,然後線程對tail域調用getAndSet方法,使自己成爲隊列的尾部,同時獲取一個指向其前趨的引用myPred,然後該線程就在前趨結點的locked字段上旋轉,直到前趨結點釋放鎖。當一個線程需要釋放鎖時,將當前結點的locked域設置爲false,同時回收前趨結點。如下圖所示,線程A需要獲取鎖,其myNode域爲true,些時tail指向線程A的結點,然後線程B也加入到線程A後面,tail指向線程B的結點。然後線程AB都在它的myPred域上旋轉,一量它的myPred結點的locked字段變爲false,它就可以獲取鎖掃行。明顯線程AmyPred locked域爲false,此時線程A獲取到了鎖。

整個CLH的代碼如下,其中用到了ThreadLocal類,將QNode綁定到每一個線程上,同時用到了AtomicReference,對尾指針的修改正是調用它的getAndSet()操作來實現的,它能夠保證以原子方式更新對象引用。

CLH鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅的狀態,如果發現前驅釋放了鎖就結束自旋。

importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;

 

public class CLHLock {

   public static class CLHNode {

       private boolean isLocked = true; // 默認是在等待鎖

    }

 

   @SuppressWarnings("unused" )

   private volatile CLHNode tail ;

    privatestatic final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER =AtomicReferenceFieldUpdater

                  . newUpdater(CLHLock.class,CLHNode .class , "tail" );

 

   public void lock(CLHNode currentThreadCLHNode) {

       CLHNode preNode = UPDATER.getAndSet( this, currentThreadCLHNode); // 轉載人註釋: this裏的"tail" 值設置成currentThreadCLHNode

       if(preNode != null) {//已有線程佔用了鎖,進入自旋

           while(preNode.isLocked ) {

           }

       }

    }

 

   public void unlock(CLHNode currentThreadCLHNode) {

       // 如果隊列裏只有當前線程,則釋放對當前線程的引用(for GC)。

       if (!UPDATER .compareAndSet(this, currentThreadCLHNode, null)) {

           // 還有後續線程

           currentThreadCLHNode. isLocked = false ;// 改變狀態,讓後續線程結束自旋

       }

    }

}

針對CLS鎖,後期優化出現了MCS鎖。MCSSpinlock 是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,直接前驅負責通知其結束自旋,從而極大地減少了不必要的處理器緩存同步的次數,降低了總線和內存的開銷。

CLH MCS的比較

下圖是CLH鎖和MCS鎖隊列圖示:

差異:

  1. 從代碼實現來看,CLH比MCS要簡單得多。
  2. 從自旋的條件來看,CLH是在前驅節點的屬性上自旋,而MCS是在本地屬性變量上自旋
  3. 從鏈表隊列來看,CLH的隊列是隱式的,CLHNode並不實際持有下一個節點;MCS的隊列是物理存在的。
  4. CLH鎖釋放時只需要改變自己的屬性,MCS鎖釋放則需要改變後繼節點的屬性。

注意:這裏實現的鎖都是獨佔的,且不能重入的。

 

 

下面從Semaphore CountDownLatch源碼實現來說明AQS

①   Semaphore

semaphore代碼執行的流程,分析acquire的過程
信號量在多線程中有着重要的應用,它的原理是將資源抽象成信號量,如果信號量大於0表明有可用資源,小於0,則需要等待。
對應申請時,就是做信號量減操作;
對應釋放時,就是做信號量加操作。
資源又抽象成state,這是個所有線程都可見的變量。通過state就可以判斷線程是不是可以訪問共享資源了。


1.new Semaphore
()對象時,它調用的 sync = new NonfairSync(permits);可以看出它是調用非公平的同步輔助類,其中sync的類型是Sync,它的定義如下:
   abstract static class Sync extendsAbstractQueuedSynchronizer
{};
   

2.NonfairSync類又繼承Sync類,它具體實現如下:
  final static class NonfairSync extends Sync{

        private static finallong serialVersionUID = -2694183684443567898L;


        NonfairSync(intpermits) {
           super(permits);
/*此處調用的是Sync(intpermits) {
                setState(permits);

             }
最終state的定義是這樣的private volatile int state;
這個state對所有的線程是可見的,並且是最新的值。
*/

        }
        //這個是在第三步中的acquireSharedInterruptibly()方法被調用
        protected inttryAcquireShared(int acquires) {

            returnnonfairTryAcquireShared(acquires);
        }
    }
   
3.看看acquire()方法的執行過程
 
先是調用如下的方法:
  public void acquire() throwsInterruptedException {

       sync.acquireSharedInterruptibly(1);
  }
  acquireSharedInterruptibly(1) 這個方法調用下面這個方法:
  public final voidacquireSharedInterruptibly(int arg) throws InterruptedException {

        if(Thread.interrupted())
            thrownew InterruptedException();
//如果小於0,表明沒有資源了,就要進入隊列中去
        if (tryAcquireShared(arg)< 0)

           doAcquireSharedInterruptibly(arg);
    }

4.tryAcquireShared()方法調用nonfairTryAcquireShared()方法,看看它的具體是怎樣實現。
  final int nonfairTryAcquireShared(intacquires) {

            for (;;){
//最新的state值
               int available = getState();

//還剩下多少個許可證了(permit)
               int remaining = available - acquires;

//只有在還剩有時才更新狀態
               if (remaining < 0 ||

                   compareAndSetState(available, remaining))
                   return remaining;
            }
        }
 
 5.如果沒有許可證了,就要進入隊列中去了,看看doAcquireSharedInterruptibly()這個方法的實現
    private voiddoAcquireSharedInterruptibly(int arg)

        throwsInterruptedException {
        final Node node =addWaiter(Node.SHARED);
/*
             private Node addWaiter(Node mode) {
                Node node = new Node(Thread.currentThread(), mode);
                Node pred = tail;
                if (pred != null) {
                    node.prev = pred;
                    if (compareAndSetTail(pred, node)) {
                        pred.next = node;
                        return node;
                    }
                }
//進入隊列
                enq(node);

                return node;
             }
*/
        try {
            for (;;){
               final Node p = node.predecessor();
               if (p == head) {
                   int r = tryAcquireShared(arg);
                   if (r >= 0) {
                       setHeadAndPropagate(node, r);
                       p.next = null; // help GC
                       return;
                   }
               }
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   break;
            }
        } catch (RuntimeExceptionex) {
           cancelAcquire(node);
            throw ex;
        }
        // Arrive here only ifinterrupted
        cancelAcquire(node);
        throw newInterruptedException();
    }

  

②    CountDownLatch

CountDownLatch源代碼分析
1.
首先分析它的功能:Asynchronization aid that allows one or more threads to wait until a set ofoperations being performed in other threads completes.
也就是說主線程在等待所有其它的子線程完成後再往下執行。常見的例子是待所有的運動員跑完後再排名。


2.
它所提供的方法如下:
構造函數:CountDownLatch(intcount)//初始化count數目的同步計數器,只有當同步計數器爲0,主線程纔會向下執行
主要方法:void await()//當前線程等待計數器爲0 
      boolean await(long timeout,TimeUnit unit)//
與上面的方法不同,它加了一個時間限制。
      void countDown()//
計數器減1
      long getCount()//
獲取計數器的值


3.
它的內部有一個輔助的內部類:sync.
它的實現如下:privatestatic final class Sync extends AbstractQueuedSynchronizer {
        private static finallong serialVersionUID = 4982264981922014374L;

        /*此處在new CountDownLatch時會調用: 
 public CountDownLatch(int count) {

             if (count < 0) throw new IllegalArgumentException("count < 0");
             this.sync = new Sync(count);
        }*/
        Sync(int count) {
           setState(count);
        }


        int getCount() {
            returngetState();
        }


        protected inttryAcquireShared(int acquires) {
            return(getState() == 0) ? 1 : -1;
        }


        protected booleantryReleaseShared(int releases) {
            //Decrement count; signal when transition to zero
            for (;;){
               int c = getState();
               if (c == 0)
                   return false;
               int nextc = c-1;
               if (compareAndSetState(c, nextc))
                   return nextc == 0;
            }
        }
    }

4.await()方法的實現
  sync.acquireSharedInterruptibly(1);

     -->if(tryAcquireShared(arg) < 0)//調用3中的tryAcquireShared()方法
           doAcquireSharedInterruptibly(arg);//
加入到等待隊列中

5.countDown
()方法的實現
  sync.releaseShared(1);

     --> if(tryReleaseShared(arg))//調用3中的tryReleaseShared()方法
              doReleaseShared();//
解鎖

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章