併發編程-同步隊列AQS

AQS是類AbstractQueuedSynchronizer的簡稱,併發包中的鎖的底層就是基於AQS實現的。

AQS的核心結構:FIFO的雙向隊列 + state(狀態信息)

首先我們說下FIFO的雙向隊列:


併發編程-同步隊列AQS

雙向隊列是在類AbstractQueuedSynchronizer類內部維護了一個Node的類,在Node中的

prev指向的當前節點的前一個節點

next指向的當前節點的後一個節點,

形成一個雙向隊列。

thread 變量用來存放進入AQS隊列的線程,

exclusive 標記線程獲取獨佔鎖被颳起後放入AQS隊列

waitStatus 記錄當前線程的等待狀態 4種狀態(CANCELLED 線程被取消, SIGNAL 線程需要被喚醒, CONDITION 線程在等待隊列, PROPAGATE釋放共享資源通知其它節點)

下圖為Node的結構


併發編程-同步隊列AQS

下面我們看下在一個線程獲取鎖失敗的時候,線程的入隊操作。首先線程會被轉化成Node節點,然後執行enq(final Node node)方法將節點插入到AQS的阻塞隊列。下面是enq方法的代碼實現:


併發編程-同步隊列AQS

通過上面的代碼我們可以發現,AQS的結構式一個鏈式存儲結構(帶頭節點)。

如果當前線程是第一次插入隊列(即tail=null的時候),需要初始化帶頭節點隊列。

上面的代碼主要的做的事情:

1.調用CAS操作初始化帶頭節點

2.不斷的發起CAS操作把在隊列的尾部插入當前線程的Node節點


state(狀態信息)

在AQS中維持了一個單一的狀態信息state,這個state信息的實際意義可以在子類中自己實現。

ReentrantLock state可以表現當前線程鎖的可重複次數。

ReentrantReadWriteLock state 的高16位表示讀狀態,低16位表示寫狀態。

CountDownLatch state 表示計數器的當前值

Semaphone state表示當前線程可用的信號個數

private volatile int state;


併發編程-同步隊列AQS

AQS 內部實現的acquire方法,如果tryAcquire()成功,那麼直接方法執行業務代碼,如果失敗會將當前線程Node.EXCLUSIVE(這裡是獨佔方式,共享方式會調用acquireShared,插入到Node.SHARED)的node節點,然後插入到AQS隊列的尾部,並且調用LockSupport.park(this),來掛起自己。

然後子類需要實現tryAcquire()方法,來定義自身嘗試獲取資源的方式,如果子類不重寫tryAcquire()方法會拋出UnsupportedOperationException異常。

例如,ReentrantLock 非公平鎖的實現如下:


併發編程-同步隊列AQS

Semaphone的tryAcquire()具體實現如下:


併發編程-同步隊列AQS

然後我們在看下是否資源的release(int arg)方法


併發編程-同步隊列AQS

這裡同樣先調用tryRelease(arg)方法,這個方法同樣需要子類實現,來嘗試釋放資源。然後在調用LockSupport.unpark(thread),這裡注意有頭節點的存在,喚醒的線程是h.next。


小結:

1.AQS是一個雙向對列,內部維護一個Node,thread變量存放等待執行的線程,prev指向前一個節點,next指向後一個節點

2.在使用AQS隊列的時候,需要自己定義維護state,並且需要重寫tryAquire(),tryRelease(),通過操作state的方法來對定義獲取資源的方式


參考文檔 《Java併發編程之美》


分享到:


相關文章: