AQS(AbstractQueuedSynchronizer)是java lock的實現基礎,如下類圖描述,juc的很多實現都依賴AQS。AQS確實比較複雜,
所有不同鎖的每個細節難以描述清楚,所以本文的重點是使用ReentrantLock作為例子只分析NonfairSync和condition的實現原理,
其它鎖的實現原理都是以此作為基礎進行擴充套件和優化。
從Lock的一段簡單程式碼開始
就以下面一段我們經常使用的簡單程式碼作為例子,例子中就是使用Lock和Condition實現執行緒安全的生產者-消費者模型,藉助這個簡單的例子,
我們一步步分析一下Lock內部是如何實現執行緒同步的。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProductQueue<T> {
private T[] items;
private Lock lock = new ReentrantLock();
final static int DEAFULT_SIZE = 10;
//滿佇列條件
private Condition notFull = lock.newCondition();
//空佇列條件
private Condition notEmpty = lock.newCondition();
private int head, tail, count;
@SuppressWarnings("unchecked")
public ProductQueue(int size) {
items = (T[]) new Object[size];
}
public ProductQueue() {
this(DEAFULT_SIZE);
}
/**
* 生產產品
*
* @param t
* @throws InterruptedException
*/
public void put(T t) throws InterruptedException {
lock.lock();//獲取獨佔鎖
try {
Thread.sleep(2 * 1000);
while (count == getCapacity()) {//佇列已滿,掛起當前 執行緒,將當前執行緒加入條件鎖佇列中,直到收到佇列不為滿的訊號,從佇列中按照FIFO喚醒一個執行緒
notFull.await(); //釋放獨佔鎖 ,讓其他執行緒(消費者執行緒)獲取,然後掛起當前執行緒,一旦後續條件滿足,再次獲取鎖
}
items[tail] = t;
if (++tail == getCapacity()) {
tail = 0;
}
++count;
notEmpty.signalAll();//佇列已不為空,可以喚醒消費者 消費產品
} finally {
lock.unlock();
}
}
/**
* 消費產品
*
* @return
* @throws InterruptedException
*/
public T get() throws InterruptedException {
lock.lock();
try {
while (count == 0) {//佇列已空,掛起空佇列鎖條件 執行緒,直到收到不為空的訊號,被喚醒
notEmpty.await();
}
T ret = items[head];
items[head] = null;
if (++head == getCapacity()) {
head = 0;
}
--count;
notFull.signalAll();//佇列已不為滿,可以喚醒生產者 生產產品
return ret;
} finally {
lock.unlock();
}
}
/**
* 產品佇列容量
*
* @return
*/
public int getCapacity() {
return items.length;
}
/**
* 當前產品數量
*
* @return
*/
public int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
複製程式碼
AQS基礎資料結構
在開始分析之前,先講一下AQS實現中的基礎資料結構(如下圖所示)
- state:32位整數描述的狀態位來支援原子操作(volatile+CAS)
- head:有序佇列的隊頭結點,tail:有序佇列的隊尾結點
- pre:當前節點的前一個結點引用 next:當前節點的下一個節點引用主要用於構建有序佇列的雙向連結串列
- thread:當前節點繫結的執行緒
- waitStatus:當前節點的等待狀態
- nextWaiter:當前節點的後繼節點,主要結合Condition使用
- firstWaiter:Condition佇列的第一個Node
- lastWaiter:Condition佇列的最後一個Node,主要用於表示Condition佇列的頭和尾節點,需要區分AQS佇列的head和tail
Lock.lock()
從Lock的lock方法作為入口講起,參考下面的處理流程圖:
整個加鎖執行邏輯:
- 1.如果tryAcquire(arg)成功,返回true,說明當前執行緒已經拿到鎖,執行當前執行緒操作,整個lock()過程就結束了。如果失敗進行操作2。
- 2.建立一個獨佔節點(同時會新生成一個傀儡頭結點)並且將此節點加入CHL佇列末尾。進行操作3。
- 3.acquireQueued(node,arg),自旋嘗試獲取鎖,如果獲取失敗,則根據前一個節點來決定是否掛起(park()),不管是否掛起,都會自旋,直到成功獲取到鎖。如果在自旋的過程中執行緒被中斷過,那麼會置執行緒中斷標誌, 進行操作4。
- 4.如果當前執行緒已經中斷過,那麼就中斷當前執行緒(清除中斷位)。
Lock.unlock()
整個解鎖執行邏輯:
- 1.tryRelease(arg)執行解鎖操作,如果鎖釋放失敗或者當前執行緒沒有持有鎖,則丟擲異常(IllegalMonitorStateException),如果成功,則進行操作2。
- 2.獲取CHL佇列頭結點,如果頭結點為空或者狀態為0,說明CHL佇列為空,結束鎖釋放過程,否則進行操作3。
- 3.找到需要喚醒的繼任節點,並進行執行緒喚醒操作(unpark()),同時會維護CHL佇列,去除已中斷或者超時的節點,結束鎖釋放過程。
AQS佇列
AQS佇列
上面lock()和unlock()2個方法的執行過程中,就維護了一個AQS佇列:
- 整個AQS佇列為帶頭結點和尾節點的雙向連結串列,其中的節點主要以waitStatus來表示其狀態,並繫結對應的執行緒。
- AQS佇列的頭節點為傀儡節點,喚醒的節點為傀儡節點的繼任節點,移除節點從佇列頭節點開始,新增節點從佇列尾節點開始,新增時主要以CAS操作保證執行緒安全。
- AQS佇列主要按FIFO來保證節點的執行順序,以此來達到公平性。
Condition.await()
執行邏輯如下:
- 1.將當前執行緒加入Condition鎖佇列。特別說明的是,這裡不同於AQS的佇列,這裡進入的是Condition的FIFO佇列。
- 2.釋放鎖,這裡可以看到將鎖釋放了,否則別的執行緒就無法拿到鎖而發生死鎖。進行3。
- 3.自旋(while)掛起,直到被喚醒或者超時或者CACELLED。進行4。
- 4.獲取鎖(acquireQueued),並將自己從Condition的FIFO佇列中釋放,表明自己不再需要鎖(我已經拿到鎖了)。
Condition.signal()
signal就是喚醒Condition佇列中的第一個非CANCELLED節點執行緒,
而signalAll就是喚醒所有非CANCELLED節點執行緒。當然了遇到CANCELLED執行緒就需要將其從FIFO佇列中剔除。
aqs和condition2個佇列
- 以生產者-消費者模型來說,在產品的生成和消費過程中,會維護3個佇列,一個AQS佇列,一個生產者條件佇列,一個消費者條件佇列。
- AQS和Condition佇列有一定的不同,條件佇列為典型的FIFO佇列,而AQS帶有一定的特性(頭結點和繼任節點關係)。
- 當執行await()會向相應的條件佇列中加入條件節點,並進行相應的維護,當執行signal()或者signalAll()時,會將條件佇列中的節點維護到AQS中,進行佇列間的互動。