AQS是類AbstractQueuedSynchronizer的簡稱,併發包中的鎖的底層就是基於AQS實現的。
AQS的核心結構:FIFO的雙向隊列 + state(狀態信息)
首先我們說下FIFO的雙向隊列:
雙向隊列是在類AbstractQueuedSynchronizer類內部維護了一個Node的類,在Node中的
prev指向的當前節點的前一個節點,
next指向的當前節點的後一個節點,
形成一個雙向隊列。
thread 變量用來存放進入AQS隊列的線程,
exclusive 標記線程獲取獨佔鎖被颳起後放入AQS隊列
waitStatus 記錄當前線程的等待狀態 4種狀態(CANCELLED 線程被取消, SIGNAL 線程需要被喚醒, CONDITION 線程在等待隊列, PROPAGATE釋放共享資源通知其它節點)
下圖為Node的結構
下面我們看下在一個線程獲取鎖失敗的時候,線程的入隊操作。首先線程會被轉化成Node節點,然後執行enq(final Node node)方法將節點插入到AQS的阻塞隊列。下面是enq方法的代碼實現:
通過上面的代碼我們可以發現,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 內部實現的acquire方法,如果tryAcquire()成功,那麼直接方法執行業務代碼,如果失敗會將當前線程Node.EXCLUSIVE(這裡是獨佔方式,共享方式會調用acquireShared,插入到Node.SHARED)的node節點,然後插入到AQS隊列的尾部,並且調用LockSupport.park(this),來掛起自己。
然後子類需要實現tryAcquire()方法,來定義自身嘗試獲取資源的方式,如果子類不重寫tryAcquire()方法會拋出UnsupportedOperationException異常。
例如,ReentrantLock 非公平鎖的實現如下:
Semaphone的tryAcquire()具體實現如下:
然後我們在看下是否資源的release(int arg)方法
這裡同樣先調用tryRelease(arg)方法,這個方法同樣需要子類實現,來嘗試釋放資源。然後在調用LockSupport.unpark(thread),這裡注意有頭節點的存在,喚醒的線程是h.next。
小結:
1.AQS是一個雙向對列,內部維護一個Node,thread變量存放等待執行的線程,prev指向前一個節點,next指向後一個節點
2.在使用AQS隊列的時候,需要自己定義維護state,並且需要重寫tryAquire(),tryRelease(),通過操作state的方法來對定義獲取資源的方式
參考文檔 《Java併發編程之美》
閱讀更多 小黃同學1024 的文章