AQS 是什么
在 Lock 中,用到了一個同步隊列 AQS,全稱 AbstractQueuedSynchronizer,它是一個同步工具也是 Lock 用來實現線程同步的核心組件。如果你搞懂了 AQS,那么 J.U.C 中絕大部分的工具都能輕松掌握。
AQS 的兩種功能
從使用層面來說,AQS 的功能分為兩種:
獨占和共享。
獨占鎖:每次只能有一個線程持有鎖,比如前面給大家演示的 ReentrantLock 就是以獨占方式實現的互斥鎖。
共 享 鎖 : 允許多個線程同時獲取鎖 , 並發訪問共享資源 , 比如ReentrantReadWriteLock。
AQS 的內部實現
AQS 隊列內部維護的是一個 FIFO 的雙向鏈表,這種結構的特點是每個數據結構都有兩個指針,分別指向直接的后繼節點和直接前驅節點。所以雙向鏈表可以從任意一個節點開始很方便的訪問前驅和后繼。每個 Node 其實是由線程封裝,當線程爭搶鎖失敗后會封裝成 Node 加入到 ASQ 隊列中去;當獲取鎖的線程釋放鎖以后,會從隊列中喚醒一個阻塞的節點(線程)。

AQS核心變量
1、volatile int state
AQS使用一個 volatile 修飾的 int 變量來表示同步狀態,當 state>0 時,表示已經獲取到了鎖,當 state=0 時,表示釋放了鎖。
它提供了三個方法:
getState()
seState(int newState)
compareAndSetState(int expect, int update)
這三個方法用於對同步狀態state進行操作,當然,AQS可以確保對state操作的安全性。
2、FIFO同步隊列
AQS通過內置的FIFO同步隊列,來完成資源獲取線程的排隊工作。
如果當前線程獲取同步狀態失敗時,AQS會將當前線程以及等待狀態等信息,構造成一個節點(Node)並將其加入同步隊列
同時,會阻塞當前線程,當同步狀態釋放時,則會把節點中的線程喚醒,使其再次嘗試獲取同步狀態。
這個所謂的FIFO就是CLH隊列:
CLHNode的組成:

釋放鎖以及添加線程對於隊列的變化(通俗的講就是入隊和出隊)
當出現鎖競爭以及釋放鎖的時候,AQS 同步隊列中的節點會發生變化,首先看一下添加節點的場景。

入隊源碼:
/** * Creates and enqueues node for current thread and given mode. * 新節點的創建並入隊在制定模式下(共享式和獨占式) * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ private Node addWaiter(Node mode) { //1、為當前線程創建新節點 Node node = new Node(Thread.currentThread(), mode); // 嘗試快速插入,如果失敗則用enq插入 //1、獲取同步器中的tail指向的節點,即:未插入新節點時的尾節點(暫且稱之為原隊列中的尾節點) Node pred = tail; if (pred != null) {//判斷尾節點不為空,為空則使用enq插入,enq中會存在創建head和Tail節點的邏輯 //2、新節點的前驅節點指向原尾節點 node.prev = pred; //3、使用CAS設置尾節點(AQS代碼風格之一:將操作放入if判斷中) if (compareAndSetTail(pred, node)) { //4、將原尾節點的next指向新節點 pred.next = node; return node; } } //如果快速插入隊列失敗,則用enq進行插入 enq(node); return node; } /** * Inserts node into queue, initializing if necessary. See picture above. * 將節點插入隊列,如果有必要(未節點為空),即:隊列為空,則初始化隊列 * @param node the node to insert * @return node's predecessor */ private Node enq(final Node node) { //類似節點獲取同步狀態時的自旋,其實就是有返回條件的死循環 for (;;) { //1、將同步器中的未節點賦給臨時變量t Node t = tail; if (t == null) { // Must initialize //2、如果未節點為空,就是隊列為空,則說明隊列為空,新建一個節點,使用CAS設置頭結點。,CAS保證操作的原子性 if (compareAndSetHead(new Node())) //3、如果頭結點設置成功,則將同步器中的未節點指向頭結點,然后繼續步驟1 tail = head; } else { //4、將新節點的前驅節點指向原隊列中的未節點 node.prev = t; //5、使用CAS設置未節點,即:同步器中的tail指向新節點 if (compareAndSetTail(t, node)) { //6、如果未節點設置成功,則將原未節點的next指向新節點 t.next = node; return t; } } } }
這里會涉及到兩個變化:
1. 新的線程封裝成 Node 節點追加到同步隊列中,設置 prev 節點以及修改當前節點的前置節點的 next 節點指向自己。
2. 通過 CAS 將tail 重新指向新的尾部節點head 節點表示獲取鎖成功的節點,當頭結點在釋放同步狀態時,會喚醒后繼節點,如果后繼節點獲得鎖成功,會把自己設置為頭結點,節點的變化過程如下:

這個過程也是涉及到兩個變化
1. 修改 head 節點指向下一個獲得鎖的節點
2. 新的獲得鎖的節點,將 prev 的指針指向 null設置 head 節點不需要用 CAS,原因是設置 head 節點是由獲得鎖的線程來完成的,而同步鎖只能由一個線程獲得,所以不需要 CAS 保證,只需要把 head 節點設置為原首節點的后繼節點,並且斷開原 head 節點的 next 引用即可。
AQS中最重要的就是aquire(int arg)
方法,它是AQS中提供的模板方法,該方法為獨占式獲取同步狀態,會忽略中斷,也就是說,由於線程獲取同步狀態失敗加入到CLH同步隊列中,后續對線程進行中斷操作時,線程不會從同步隊列中移除。
acquire方法流程圖如下:
