一、AQS--鎖的底層支持
1.AQS是什么
AQS是AbstractQueuedSychronizer的簡稱,即抽象同步佇列的簡稱,這是實作同步器的重要組件,是一個抽象類,雖然在實際作業中很燒用到它,但是了解它的內部原理是很有必要的,并法包中鎖的底層就是使用該抽象類實作的,下面類圖 
2.分析AQS類
AQS是一個雙向佇列,head和tail變數型別是Node型別,分別用于表示佇列的隊首和隊尾 在AQS中維護一個單一的狀態資訊state,可以通過getState,setState、compareAndSetState函式來修改其值, 對于ReentrantLock的實作來說,state可以用來表示當前執行緒獲取鎖的可重入次數;對于讀寫鎖ReentrantReadWriteLock來說,state的高16位表示讀狀態,也就是獲取該讀鎖的次數,低16位表示獲取到寫鎖的程式可重入次數;對于semaphore來說,state用來表示當前可用信號的個數;對于CountDownlatch來說,state用來表示計算器當前的值, 對于AQS來說,執行緒同步最關鍵的就是對state的操作,根據state是否屬于同一個執行緒,操作state的方式分為獨占式和共享方式 在獨占方式下,獲取和釋放執行緒的方法為:void acquire(int arg) void acquireInterruptilbly(int arg) boolean release(int arg) 在共享方式下,獲取和釋放執行緒的方法為:void acquireShared(int arg) void acquireSharedInterruptibly(int arg) boolean releaseShared(int arg) 使用獨占式獲取資源是與具體執行緒系結的,一個執行緒獲取到了資源,就會標記這個執行緒獲取到了,其他執行緒再嘗試操作state獲取資源時會發現當前資源不是自己持有的,就會在獲取失敗之后被阻塞,比如獨占鎖ReentrantLock的實作,在AQS內部首先會使用CAS操作把state從0變成1,然后設定當前鎖的持有者是當前執行緒,當執行緒再次獲取鎖時發現鎖的持有者就是自己,則會把狀態值從1變成2,也就是設定可重入次數,而當另外一個執行緒獲取鎖的時候發現自己不是鎖的持有者,就會被放入AQS阻塞佇列之中, 對應共享方式的資源是與具體執行緒不相關的,當多個執行緒使用CAS操作去競爭資源的時候,當一個執行緒獲取到了資源,另外一個資源只需要使用CAS操作獲取即可,例如:Semaphore信號量,當一個執行緒通過acquire獲取信號量的時候,會首先看當前信號量個數是否滿足需要,不滿足則把當前執行緒放入到阻塞佇列中,如果滿足則通過CAS操作獲取信號量,會首先看當前信號量個數是否滿足需要,不滿足則把當前執行緒放入阻塞佇列,如果滿足就會通過自旋CAS獲取信號量
3.分析Node內部類
變數thread是一個Thread型別,用于存放進入AQS佇列的執行緒 看一下幾個Node型別代表的含義 SHARED用來標記該執行緒是獲取共享資源的時候被阻塞掛起后放入AQS佇列的; EXCLUSIVE用來標記執行緒是獲取獨占資源時被掛起后放入AQS佇列的; waitStatus是用來記錄執行緒等待狀態的,可以為CANCELLED(執行緒被取消了),SIGNAL(執行緒需要被喚醒)、CONDITION(執行緒在條件佇列里面等待)、PROPAGATE(釋放資源的時候需要通知其他節點) prev和next分別代表當前節點的前驅節點和后置節點,
4.AQS內部ConditionObject
這個類是用來實作執行緒同步的,ConditionObject可以直接訪問AQS物件內部的變數,比如state狀態值和AQS佇列,ConditonObject是條件變數,每個條件變數對應一個條件佇列(單向鏈表列隊),其用來存放呼叫條件變數的await方法后被阻塞的執行緒,如類圖所示,這個條件佇列的頭尾元素分別為firstWaiter和lastWaiter,
5.獨占方式下,獲取與釋放資源是如何及逆行的
當一個執行緒呼叫acquire(int arg)獲取資源的時候,會首先使用tryAcquire「嘗試」獲取資源,具體就是設定state值,成功則直接回傳,失敗則會當前執行緒封裝為Node.EXCLUSIVE的Node節點,插入到AQS阻塞佇列的隊尾,并且呼叫LockSupport.park(this)方法掛起自己
public final void acquire(int arg) {
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) {
selfInterrupt();
}
}
當一個執行緒呼叫release(int arg)方法的時候,會嘗試使用tryRelease操作來釋放資源,也就是設定state的值,然后呼叫LockSupport.unpark(thread)方法激活在AQS佇列被阻塞的頭部的一個執行緒,被激活的執行緒,然后使用tryAcquire嘗試,看當前狀態值state值是否滿足自己的需要,如果滿足,則激活執行緒,繼續向下運行,否則還是會被放回AQS佇列中,然后被掛起
public final boolean release(int arg) {
if(tryRelease(arg)) {
Node h = head;
if(head != null && head.waitStatus != 0) {
unparkSuccessor(h);
}
return true;
}
return false;
}
AQS中的tryAcquire和tryRelease方法是沒有提供具體實作的,需要程式員自行在子類中進行定義,它們的實作就是使用CAS演算法對state進行修改,成功回傳true,失敗回傳false,子類還需要定義當呼叫acquire和release方法時state狀態值的增減分別代表什么含義, 比如繼承自AQS中的獨占鎖ReentrantLock,定義當status為0的時候表示鎖空閑,1表示該鎖正在占用,重寫tryAcquire方法,就是使用CAS演算法,查看state是否為0,如果為0,那么置為1,并且回傳true,否則,回傳false;獨占鎖在實作release的時候,在內部使用CAS演算法把當前state的值從1修改為0,并且設定當前執行緒的持有者為null,然后回傳true,如果CAS失敗,那么回傳false
6.共享方式下,獲取與釋放資源是如何及逆行的
描述基本和獨占鎖的方式一樣 當一個執行緒呼叫acquireShared(int arg)獲取資源的時候,會首先使用tryAcquireShared「嘗試」獲取資源,具體就是設定state值,成功則直接回傳,失敗則會當前執行緒封裝為Node.SHARED的Node節點,插入到AQS阻塞佇列的隊尾,并且呼叫LockSupport.park(this)方法掛起自己
public final void acquireShared(int arg) {
if(tryAcquireShared(arg) < 0) {
doAcquireShared(arg);
}
}
當一個執行緒呼叫releaseShared(int arg)方法的時候,會嘗試使用tryRelease操作來釋放資源,也就是設定state的值,然后呼叫LockSupport.unpark(thread)方法激活在AQS佇列被阻塞的頭部的一個執行緒,被激活的執行緒,然后使用tryAcquire嘗試,看當前狀態值state值是否滿足自己的需要,如果滿足,則激活執行緒,繼續向下運行,否則還是會被放回AQS佇列中,然后被掛起
public final boolean releaseShared(int arg) {
if(tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
AQS類并沒有提供可用的tryAcquireShared和tryReleaseShared方法,正如AQS是鎖阻塞和同步器的基礎框架一樣,tryAcquireShared和tryReleaseShared需要由具體的子類進行實作,子類在實作tryAcquiredShared和tryReleaseShared時要根據具體場景使用CAS演算法嘗試修改state狀態變數,成功則回傳true,否則回傳false 比如繼承自AQS實作的讀寫鎖ReentrantReadWriteLock里面的讀鎖在重寫tryAcquireShared時,首先查看寫鎖是否被其他執行緒持有,如果是則直接回傳false,否則使用CAS遞增的state的高16位(在ReentrantReadWriteLock中,state的高16位為獲取讀鎖的次數) 比如繼承自AQS實作的讀寫鎖ReentrantReadWriteLock里面的讀鎖在重寫tryReleaseShared時,在內部需要使用CAS演算法把當前state的值高16位減1,然后回傳true,如果CAS失敗那么回傳false 基于AQS實作的鎖除了需要重寫上述這些方法之外,還需要重寫isHeldExclusively方法,來判斷鎖是被當前執行緒占用還是被共享
7.另外對與獨占方式下void acquire(int arg)和void acquireInterruptibly(int arg),與共享方式下void acquireShared(int arg)和void acquireSharedInterruptibly(int arg)之間有一個單詞Interruptibly的區別是什么
不帶interruptibly的方法意思就是不對中斷進行回應,比如執行緒在呼叫了不帶Interruptibly的方法獲取資源或者獲取資源失敗被掛起的時候,其他執行緒中斷了該執行緒,那么該執行緒不會因為被中斷而拋出例外,它還是繼續獲取資源或者被掛起,也就是不對中斷進行回應,忽略中斷 帶有該單詞的方法要對中斷進行回應,也就是執行緒在呼叫了帶有該單詞的方法獲取資源獲取獲取資源失敗被掛起的時候,其他執行緒中斷了該執行緒,該執行緒就會拋出InterruptException例外而回傳
二、原始碼:
所在包:com.ruigege.ConcurrentListSouceCodeAnalysis5 https://github.com/ruigege66/ConcurrentJavaCSDN:https://blog.csdn.net/weixin_44630050 博客園:https://www.cnblogs.com/ruigege0000/ 歡迎關注微信公眾號:傅里葉變換,個人賬號,僅用于技術交流 
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/255053.html
標籤:Java
