1. 程式人生 > >AQS原理與原始碼

AQS原理與原始碼

一、細說AQS

在深入分析AQS之前,我想先從AQS的功能上說明下AQS,站在使用者的角度,AQS的功能可以分為兩類:獨佔鎖和共享鎖。它的所有子類中,要麼實現並使用了它獨佔鎖的API,要麼使用了共享鎖的API,而不會同時使用兩套API,即便是它最有名的子類ReentrantReadWriteLock,也是通過兩個內部類:讀鎖和寫鎖,分別實現的兩套API來實現的,到目前為止,我們只需要明白AQS在功能上有獨佔鎖和共享鎖兩種功能即可。

二、ReentrantLock的呼叫過程

經過觀察ReentrantLock把所有Lock介面的操作都委派到一個Sync類上,該類繼承了AbstractQueuedSynchronizer:

  1. abstractstaticclass Sync extends AbstractQueuedSynchronizer  

Sync又有兩個子類:

  1. /** 
  2.      * Sync object for non-fair locks 
  3.      */
  4.     staticfinalclass NonfairSync extends Sync  

  1. /** 
  2.     * Sync object for fair locks 
  3.     */
  4.    staticfinalclass FairSync extends Sync  
顯然是為了支援公平鎖和非公平鎖而定義,預設情況下為非公平鎖。
先理一下Reentrant.lock()方法的呼叫過程(預設非公平鎖):


AbstractQueuedSynchronizer中抽象了絕大多數Lock的功能,而只把tryAcquire方法延遲到子類中去實現。tryAcquire方法的語義在於用具體子類判斷請求執行緒是否可以獲得鎖,無論成功與否AbstractQueuedSynchronizer都將處理後面的流程。

三、獲取鎖的過程

簡單說來,AbstractQueuedSynchronizer會把所有的請求執行緒構成一個CLH佇列,當一個執行緒執行完畢(lock.unlock())時會啟用自己的後繼節點,但正在執行的執行緒並不在佇列中,而那些等待執行的執行緒全部處於阻塞狀態。


四、非公平鎖的加鎖流程

(1). 首先我們分析非公平鎖的的請求過程。我們假設在這個時候,還沒有任務執行緒獲取鎖,這個時候,第一個執行緒過來了(我們使用的是非公平鎖),那麼第一個執行緒thread1會去獲取鎖,這時它會呼叫下面的方法,通過CAS的操作,將當前AQS的state由0變成1,證明當前thread1已經獲取到鎖,並且將AQS的exclusiveOwnerThread設定成thread1,證明當前持有鎖的執行緒是thread1。:
  1. /** 
  2.  * Performs lock.  Try immediate barge, backing up to normal 
  3.  * acquire on failure. 
  4.  */
  5. finalvoid lock() {  
  6.     // 如果鎖沒有被任何執行緒鎖定且加鎖成功則設定當前執行緒為鎖的擁有者
  7.     if (compareAndSetState(01))  
  8.         setExclusiveOwnerThread(Thread.currentThread());  
  9.     else
  10.         acquire(1);  
  11. }  

(2). 此時來了第二個執行緒thread2,並且我們假設thread1還沒有釋放鎖,因為我們使用的是非公平鎖,那麼thread2首先會進行搶佔式的去獲取鎖,呼叫NonFairSync.lock方法獲取鎖。NonFairSync.lock方法的第一個分支是通過CAS操作獲取鎖,很明顯,這一步肯定會失敗,因為此時thread1還沒有釋放鎖。那麼thread2將會走NonFairSync.lock方法的第二個分支,進行acquire(1)操作。acquire(1)其實是AQS的方法,acquire(1)方法內部首先呼叫tryAcquire方法,ReentrantLock.NonFairLock重寫了tryAcquire方法,並且ReentrantLock.NonFairLock的tryAcquire方法又呼叫了ReentrantLock.Sync的nonfairTryAcquire方法,nonfairTryAcquire方法如下:

  1. /** 
  2.         * Performs non-fair tryLock.  tryAcquire is 
  3.         * implemented in subclasses, but both need nonfair 
  4.         * try for trylock method. 
  5.         */
  6.        finalboolean nonfairTryAcquire(int acquires) {  
  7.            final Thread current = Thread.currentThread();  
  8.            int c = getState();  
  9.            if (c == 0) {  
  10.                if (compareAndSetState(0, acquires)) {  
  11.                    setExclusiveOwnerThread(current);  
  12.                    returntrue;  
  13.                }  
  14.            }  
  15.            elseif (current == getExclusiveOwnerThread()) {  
  16.                int nextc = c + acquires;  
  17.                if (nextc < 0// overflow
  18.                    thrownew Error("Maximum lock count exceeded");  
  19.                setState(nextc);  
  20.                returntrue;  
  21.            }  
  22.            returnfalse;  
  23.        }  

這個方法的執行邏輯如下:

1. 獲取當前將要去獲取鎖的執行緒,在此時的情況下,也就是我們的thread2執行緒。

2. 獲取當前AQS的state的值。如果此時state的值是0,那麼我們就通過CAS操作獲取鎖,然後設定AQS的exclusiveOwnerThread為thread2。很明顯,在當前的這個執行情況下,state的值是1不是0,因為我們的thread1還沒有釋放鎖。

3. 如果當前將要去獲取鎖的執行緒等於此時AQS的exclusiveOwnerThread的執行緒,則此時將state的值加1,很明顯這是重入鎖的實現方式。在此時的執行狀態下,將要去獲取鎖的執行緒不是thread1,也就是說這一步不成立。

4. 以上操作都不成立的話,我們直接返回false。

既然返回了false,那麼之後就會呼叫addWaiter方法,這個方法負責把當前無法獲取鎖的執行緒包裝為一個Node新增到隊尾。通過下面的程式碼片段我們就知道呼叫邏輯:

  1. publicfinalvoid acquire(int arg) {  
  2.         if (!tryAcquire(arg) &&  
  3.             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.             selfInterrupt();  
  5.     }  

我們進入到addWaiter方法內部去看:

  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.     }  
很明顯在addWaiter內部:

第一步:將當前將要去獲取鎖的執行緒也就是thread2和獨佔模式封裝為一個node物件。並且我們也知道在當前的執行環境下,執行緒阻塞佇列是空的,因為thread1獲取了鎖,thread2也是剛剛來請求鎖,所以執行緒阻塞佇列裡面是空的。很明顯,這個時候佇列的尾部tail節點也是null,那麼將直接進入到enq方法。

第二步:我們首先看下enq方法的內部實現。首先內部是一個自懸迴圈。

  1. private Node enq(final Node node) {  
  2.         for (;;) {  
  3.             Node t = tail;  
  4.             if (t == null) { // Must initialize
  5. 相關推薦

    AQS原理原始碼

    一、細說AQS 在深入分析AQS之前,我想先從AQS的功能上說明下AQS,站在使用者的角度,AQS的功能可以分為兩類:獨佔鎖和共享鎖。它的所有子類中,要麼實現並使用了它獨佔鎖的API,要麼使用了共享鎖的API,而不會同時使用兩套API,即便是它最有名的子類Reentra

    OpenCV學習筆記(31)KAZE 演算法原理原始碼分析(五)KAZE的原始碼優化及SIFT的比較

      KAZE系列筆記: 1.  OpenCV學習筆記(27)KAZE 演算法原理與原始碼分析(一)非線性擴散濾波 2.  OpenCV學習筆記(28)KAZE 演算法原理與原始碼分析(二)非線性尺度空間構建 3.  Op

    OpenCV學習筆記(30)KAZE 演算法原理原始碼分析(四)KAZE特徵的效能分析比較

          KAZE系列筆記: 1.  OpenCV學習筆記(27)KAZE 演算法原理與原始碼分析(一)非線性擴散濾波 2.  OpenCV學習筆記(28)KAZE 演算法原理與原始碼分析(二)非線性尺度空間構

    SURF演算法原理原始碼分析

    如果說SIFT演算法中使用DOG對LOG進行了簡化,提高了搜尋特徵點的速度,那麼SURF演算法則是對DoH的簡化與近似。雖然SIFT演算法已經被認為是最有效的,也是最常用的特徵點提取的演算法,但如果不借助於硬體的加速和專用影象處理器的配合,SIFT演算法以現有的計算機仍然很難達到實時的程度。對於需要

    SIFT原理原始碼分析:DoG尺度空間構造

    《SIFT原理與原始碼分析》系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548 尺度空間理論   自然界中的物體隨著觀測尺度不同有不同的表現形態。例如我們形容建築物用“米”,觀測分子、原子等用“納米”。

    Shuffle操作的原理原始碼分析

    普通的shuffle操作 第一個特點,     在Spark早期版本中,那個bucket快取是非常非常重要的,因為需要將一個ShuffleMapTask所有的資料都寫入記憶體快取之後,才會重新整理到磁碟。但是這就有一個問題,如果map side資料過多

    TaskScheduler原理原始碼分析

    接DAGScheduler原始碼分析stage劃分演算法,task最佳位置計算演算法taskScheduler的submitTask()方法 在TaskScheduler類中 ①在SparkContext原始碼分析中,會建立TaskScheduler的時候,會建立一個SparkDe

    探祕Dubbo原理原始碼

    因為喜歡,所以我們才要學習 這一套Dubbo原理與原始碼課程,需要認真研究,每天花二個小時左右,一年後你回頭看,會發現跟以前完全不同的自己 一階段《探祕Dubbo 原理與原始碼》彙總 目錄:第1講 (免費) 除錯環境搭建-zookeeper方式第2講 (免費) 除錯環境搭建-multicast方式第3講

    探祕Dubbo原理原始碼及實操

    閱讀原始碼的作用 提取設計思路,增強設計能力 理解執行機制,便於快速解決問題以及功能擴充套件 常見有關dubbo的問題 dubbo的負載均衡是在哪個元件中處理的? dubbo預設的負載均衡演算法是什麼? 如果註冊中心掛掉了客戶端是否能夠繼續呼叫dubbo? 一個請求從

    OTSU閾值分割演算法原理原始碼

    OTSU閾值分割 OTSU閾值處理(最大類間方差),演算法步驟如下: 【1】統計灰度級中每個畫素在整幅影象中的個數。 【2】計算每個畫素在整幅影象的概率分佈。 【3】對灰度級進行遍歷搜尋,計算當前灰度值下前景背景類間概率。 【4】通過目標函式計算出類內與類間方差下對應的閾值

    執行緒池的工作原理原始碼解讀及各常用執行緒池的執行流程圖

    有時候花了大把時間去看一些東西卻看不懂,是很 “ 藍瘦 ” 的,花時間也是投資。 本文適合: 曾瞭解過執行緒池卻一直模模糊糊的人 瞭解得差不多卻對某些點依然疑惑的   隨著cpu核數越來越多,不可避免的利用多執行緒技術以充分利用其計算能力。所以,多執

    Spring原理原始碼分析系列(七)- Spring AOP實現過程實戰

    二、Spring AOP 1、什麼是Spring AOP Spring AOP是Spring核心框架的重要組成部分,採用Java作為AOP的實現語言。與AspectJ實現AOP方式不同之處在於,Spring AOP僅支援方法級別的攔截。 2、

    Android系統原理原始碼分析(1):利用Java反射技術阻止通過按鈕關閉對話方塊

    本文為原創,如需轉載,請註明作者和出處,謝謝!     眾所周知,AlertDialog類用於顯示對話方塊。關於AlertDialog的基本用法在這裡就不詳細介紹了,網上有很多,讀者可以自己搜尋。那

    【特徵匹配】RANSAC演算法原理原始碼解析

    轉載請註明出處:http://blog.csdn.net/luoshixian099/article/details/50217655 勿在浮沙築高臺   隨機抽樣一致性(RANSAC)演算法,可以在一組包含“外點”的資料集中,採用不斷迭代的方法,尋找最優引數模型,不符合最

    併發程式設計(十一)—— Java 執行緒池 實現原理原始碼深度解析(一)

    史上最清晰的執行緒池原始碼分析 鼎鼎大名的執行緒池。不需要多說!!!!! 這篇部落格深入分析 Java 中執行緒池的實現。 總覽 下圖是 java 執行緒池幾個相關類的繼承結構:    先簡單說說這個繼承結構,Executor 位於最頂層,也是最簡單的,就一個 execute(

    併發程式設計(十二)—— Java 執行緒池 實現原理原始碼深度解析 之submit方法 (二)

    在上一篇《併發程式設計(十一)—— Java 執行緒池 實現原理與原始碼深度解析(一)》中提到了執行緒池ThreadPoolExecutor的原理以及它的execute方法。這篇文章是接著上一篇文章寫的,如果你沒有閱讀上一篇文章,建議你去讀讀。本文解析ThreadPoolExecutor#submit。  

    AQS簡介原始碼剖析

    如圖4所示,如果前驅節點不是頭節點或者未成功獲取鎖則根據前驅節點和當前執行緒節點判斷是否要掛起。如果阻塞過程中被中斷,則置中斷標誌位為true.acquireQueued的返回值代表的是是否被中斷,執行緒的中斷狀態為假,如果發生中斷則要重新設定中斷狀態,會通過selfInterrupt設定回去,其實acqui

    Android Wi-Fi P2P原理原始碼學習

    一,Wi-Fi P2P相關知識 (一)P2P及其依賴的技術項 (二)P2P工作流程 包括1.裝置的發現、2.組協調、3.認證關聯、4.WPS以及4次握手。總體流程如下圖:   1.  Device

    深入理解Spark 2.1 Core (一):RDD的原理原始碼分析

    本文連結:http://blog.csdn.net/u011239443/article/details/53894611 該論文來自Berkeley實驗室,英文標題為:Resilient Distributed Datasets: A Fault-Toler

    深入理解Spark ML:多項式樸素貝葉斯原理原始碼分析

    貝葉斯估計 如果一個給定的類和特徵值在訓練集中沒有一起出現過,那麼基於頻率的估計下該概率將為0。這將是一個問題。因為與其他概率相乘時將會把其他概率的資訊統統去除。所以常常要求要對每個小類樣本的概率估計進行修正,以保證不會出現有為0的概率出現。常用到