概覽:
- 在併發編程中不得不提到的就是AQS(AbstractQueueSynchronizer)抽象同步隊列,它是實現同步器的基礎組件,併發包中鎖的底層實現就是使用AQS來實現的。AQS的結構圖如下:
- 從該圖中可以看到AQS是一個
FIFO(先進先出)雙向隊列
,其內部節點通過head和tail記錄隊首和隊尾元素,隊列元素的類型爲Node,其中Node中的thread變量用來存放AQS隊列中的線程;Node節點內部的SHARED
用來標記該線程是獲取共享資源
時被阻塞掛起後放入AQS隊列的,EXCLUSIVE
用來標記線程是獲取獨佔資源
時被掛起後放入AQS隊列的;waitStatus是記錄當前線程的等待狀態,可以爲CANCELLED(線程被取消)、SIGNAL(線程需要被喚醒)、CONDITION(線程在條件隊列中等待)、PROPAGATE(釋放共享資源時需要通知其它節點);prev記錄當前節點的前驅節點,next記錄當前節點的後繼節點。 - 在AQS中維護了一個單一的狀態信息
state
,可以通過getState、setState、CompareAndSetState函數來修改其值。- 對於ReentrantLock的實現來說,state可以用來表示當前線程獲取鎖的
可重入次數
; - 對於ReentrantReadWriteLock來說,state
高16
爲表示讀
狀態,也就是獲取該讀鎖的次數,低16位
表示獲取到寫鎖
的的線程的可重入次數; - 對於Semaphore來說,state用來表示當前
可用信號
的個數, - 對於CountDownlatch來說,state用來表示
計數器
當前的值。
- 對於ReentrantLock的實現來說,state可以用來表示當前線程獲取鎖的
- AQS有個內部類ConditionObject,用來結合鎖實現線程同步。ConditionObject可以直接訪問AQS對象內部的變量,比如state狀態值和AQS隊列,ConditionObject是條件變量,每個條件變量對應一個條件隊列(單向鏈表隊列),其用來存放調用條件變量的await方法後被阻塞的線程。
- 對於AQS來說,線程同步的關鍵是對狀態值state進行操作,根據state是否屬於一個線程,操作state的方式分爲
;
。
在獨佔方式下獲取和釋放資源的方法爲:
void acquire( int arg)
void acquirelnterruptibly(int arg)
boolean release( int arg)
- 使用獨佔鎖方式獲取資源是跟線程綁定的,就是說如果一個線程獲取到了資源,就會標記是這個線程獲取了,其它線程再嘗試操作state獲取資源時發現當前資源不是自己持有的,就會獲取失敗後被阻塞。比如獨佔鎖ReentrantLock的實現,當一個線程獲取了ReentrantLock的鎖後,在AQS內部會首先使用CAS操作把state狀態值從0變爲1,然後設置當前鎖的持有者爲當前線程,當該線程再次獲取鎖時發現鎖的持有者是自己,那麼就會把state加1,也就是可以設置重入次數,而當另一個線程獲取鎖時發現自己並不是該鎖的持有者就會被放入AQS阻塞隊列後掛起。
在共享方式下獲取和釋放資源的方法爲:
void acquireShared(int arg)
void acquireSharedinterruptibly(int arg)
boolean reeaseShared(int arg)
- 對於共享式來說,當多個線程去請求共享資源時通過CAS方式競爭獲取資源,當一個線程獲取到資源之後,另一個線程再去獲取時如果當前資源還能滿足它的需要,則當前線程只需要通過CAS方法進行獲取即可。比如Semaphore信號量,當一個線程通過acquire()方式獲取信號量時,會首先查看當前信號量個數是否滿足需要,不滿足則把當前線程放入阻塞隊列,如果滿足則通過自旋CAS獲取信號量。
在獨佔方式下,獲取與釋放資源的流程如下:
-
當一個線程調用
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隊列裏面被阻塞的一個線程(thread)。被激活的線程則使用tryAcquire
嘗試,看當前狀態變量state的值是否滿足自己的需要,滿足則激活該線程,然後繼續向下運行,否則還會被放入AQS隊列並掛起。public final boolean release (int arg) { if (tryRelease(arg )) { Node h = head; if (h 1= null && h .waitStatus != 0) unparkSuccessor(h); return true ; } return false; }
-
需要注意的是,AQS類並沒有提供可用的tryAcquire和tryRelease方法,正如AQS的鎖阻塞和同步器的基礎框架一樣,tryAcquire和tryRelease需要具體的子類來實現。子類在實現tryAcquire和tryRelease時要根據具體場景使用CAS算法嘗試修改state的值,成功則返回true,否則返回false。子類還需要定義在調用acquire和release方法時state狀態值的增減代表什麼含義。
-
比如繼承自AQS實現的獨佔鎖ReentrantLock,定義當state爲0時表示鎖空閒,不爲0時表示鎖已經被佔用,在重寫tryAcquire時,在內部需要使用CAS算法查看當前state是否爲0,如果爲0則使用CAS設置爲1,並設置鎖的持有者爲當前線程,而後返回true,如果CAS失敗則返回false。
在共享方式下獲取與釋放鎖的流程如下:
-
當線程調用
acquireShared(int arg)
獲取共享資源時,會首先使用trγAcquireShared
嘗試獲取資源,具體是設置狀態變量state的值,成功則直接返回,失敗則將當前線程封裝爲Node.SHARED
的Node節點後插入到AQS阻塞隊列的尾部,並使用LockSupport.park(this)
方法掛起自己。public final void acquireShared(int arg){ if(tryAcquireShared(arg)<0) doAcquireShared(arg) }
-
當一個線程調用
releaseShared(int arg)
時會嘗試使用tryReleaseShared
操作釋放資源,這裏是設置狀態變量state的值,然後使用LockSupport.unpark(thread)
激活AQS隊列裏面被阻塞的一個線程(thread)。被激活的線程則使用tryReleaseShared
查看當前狀態變量state的值是否滿足自己的需要,滿足則激活該線程,然後繼續向下運行,否則還是放入AQS的阻塞隊列並掛起。public final boolean releaseShared(int arg){ if(tryReleaseShared(arg)){ doReleaseShared(); return true; } return false; }
-
同樣需要注意的是,AQS類並沒有提供可用
tryAcquireShared
和tryReleaseShared
方法,正如AQS是鎖阻塞和同步器的基礎框架一樣,tryAcquireShared和tryReleaseShared需要由具體的子類來實現。子類在實現tryAcquireShared和tryReleaseShared時要根據具體場景使用CAS算法嘗試修改state的值,成功則返回true,否則返回false。 -
比如繼承自AQS實現的讀寫鎖
ReentrantReadWriteLock
裏面的讀鎖在重寫tryAcquireShared時,首先查看寫鎖是否被其它線程持有,如果是則直接返回false,否則使用CAS遞增state的高16位
(在ReentrantReadWriteLock中,state的高16位爲讀鎖的次數。)在重寫tryReleaseShared時,其內部使用CAS算法把當前state的值高16位減1,然後返回true,否則返回false。
以上就是自己整理的AQS的一部分知識,AQS的知識點很多慢慢整理。我是會敲代碼的湯姆貓!