Java多線程:AQS

Java多線程:線程間通訊之Lock中咱們提到了ReentrantLock是API級別的實現,可是沒有說明其具體實現原理。實際上,ReentrantLock的底層實現使用了AQS(AbstractQueueSynchronizer)。AQS自己僅僅是一個框架,定義了一套多線程訪問共享資源的同步框架,能夠實現ReentrantLock, Semaphore, CountDownLatch等多線程類。html

AQS框架維護了一個資源state(volatile int)和一個同步隊列。其中對state的訪問包括三種方法:getState(), setState(), compareAndSetState()。其中,compareAndSetState()是原子操做,底層是CAS實現。java

AQS框架包含兩種可供選擇的實現方式:獨佔(Exclusive)和共享(Share)。因爲不一樣自定義同步器徵用共享資源的方式不一樣,自定義同步器實現時只需實現共享資源state的獲取與釋放方式便可,而不須要考慮隊列的維護。下面簡述AQS框架中獨佔鎖和共享鎖的獲取,釋放流程。多線程

獨佔鎖流程

獲取時首先調用acquire(acquires),以後進入tryAcquire(acquires)嘗試獲取鎖,若成功則返回。若失敗則將當前線程構造爲Node節點,CAS插入到同步隊列尾部,該線程自旋。自旋時判斷其前驅節點是否爲頭節點,是否成功獲取同步狀態,兩者皆成立則當前節點設置爲頭節點,不然掛起當前線程等待被前驅節點喚醒。併發

釋放時首先調用release(acquires),以後進入tryRelease(acquires)釋放同步狀態,以後獲取同步隊列中當前節點的下一節點並喚醒。框架

共享鎖流程

獲取時首先調用acquireShared(acquires),以後進入tryAcquireShared(acquires)獲取同步狀態,返回值不小於0則說明同步狀態有剩餘,獲取成功直接返回。若返回值小於0則說明獲取同步狀態失敗,構造Node節點CAS插入同步隊列尾部並自旋檢查前驅節點是否爲頭節點且成功獲取同步狀態,如果則當前節點設爲頭節點,不然掛起等待被前驅節點喚醒。ui

釋放時調用releaseShared(acquires)釋放同步狀態,以後遍歷整個隊列喚醒全部後繼節點。.net

獨佔鎖和共享鎖實現區別

  • 獨佔鎖的state值爲1,同一時刻只有一個線程成功獲取同步狀態。共享鎖state>1,取值由自定義同步器決定。
  • 獨佔鎖隊列頭節點運行完畢釋放鎖後喚醒直接後繼節點,共享鎖喚醒全部後繼節點。
  • 共享鎖會出現多個線程同時成功獲取同步狀態的狀況。

重入鎖的實現

Java中的ReentrantLock和synchronized都是可重入鎖,synchronized由JVM實現,重入鎖實現時最主要的邏輯是判斷上次獲取鎖的線程是否爲當前線程,ReentrantLock基於AQS實現,提供公平鎖和非公平鎖兩種方式,非公平鎖實現邏輯以下:線程

final boolean nonfairTryAcquire(int acquires) {
            //獲取當前線程
            final Thread current = Thread.currentThread();
            //經過AQS獲取同步狀態
            int c = getState();
            //同步狀態爲0,說明臨界區處於無鎖狀態,
            if (c == 0) {
                //修改同步狀態,即加鎖
                if (compareAndSetState(0, acquires)) {
                    //將當前線程設置爲鎖的owner
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //若是臨界區處於鎖定狀態,且上次獲取鎖的線程爲當前線程
            else if (current == getExclusiveOwnerThread()) {
                 //則遞增同步狀態
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

公平鎖的實現邏輯以下,與非公平鎖的區別爲判斷當前節點是否存在前驅節點,只有等待前驅節點釋放後才能獲取鎖。3d

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //此處爲公平鎖的核心,即判斷同步隊列中當前節點是否有前驅節點
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

讀寫鎖的實現

Java的ReentrantReadWriteLock是讀寫鎖實現,其原理是將state變量的高16位和低16位拆分,高16位表示讀鎖,低16位表示寫鎖。其寫鎖tryAcquire(acquires)實現以下:code

  • 獲取同步狀態,分離出低16位的寫鎖狀態。
  • 同步狀態不爲0,則存在讀鎖或寫鎖。
  • 若存在讀鎖,則不能獲取寫鎖。
  • 若當前線程不是上次獲取寫鎖的線程,則不能獲取寫鎖。
  • 以上判斷經過,對低16位(寫鎖同步狀態)進行CAS修改。
  • 當前線程設爲寫鎖的獲取線程。

其讀鎖的tryAcquire(acquires)實現以下:

  • 獲取當前同步狀態,計算高16位爲讀鎖狀態+1後的值。
  • 若大於能獲取到的讀鎖的最大值,則拋出異常。
  • 若存在寫鎖且當前線程不是寫鎖獲取者,則獲取讀鎖失敗。
  • 若上述判斷都經過,則利用CAS從新設置讀鎖的同步狀態。

寫寫鎖釋放與普通獨佔鎖基本相同,在寫鎖釋放中不斷減小讀鎖的同步狀態,同步狀態爲0時才能徹底釋放;讀鎖釋放過程當中不斷釋放寫鎖狀態,直到爲0,表示沒有線程獲取讀鎖。

參考文獻

Java技術之AQS詳解
Java併發-AQS及各類Lock鎖的原理

相關文章
相關標籤/搜索