1.JDK8的AQS源碼學習
1.1.帶着問題學習
1.AQS是什麼鬼東西
2.AQS是怎麼實現的
1.2.基本介紹
在併發編程中,Doug Lea大師爲我們提供了大量實用,高性能的工具類,Doug Lea,concurrent 包的作者,編程不識Doug Lea,寫盡java也枉然。
// 同步隊列,是一個帶頭結點的雙向鏈表,用於實現鎖的語義
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements Serializable {
......
}
所謂AQS,指的是AbstractQueuedSynchronizer,它提供了一種實現阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架,ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等併發類均是基於AQS來實現的,具體用法是通過繼承AQS實現其模板方法,然後將子類作爲同步組件的內部類。
註釋:後面學習ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier的時候會看到均是基於AQS來實現的這裏不做拓展。
1.3.基本框架
AQS是一個同步器,設計模式是模板模式。
核心數據結構:雙向鏈表 + state(鎖狀態)
底層操作:CAS
AQS基本框架如下圖所示:
1.3.1AbstractQueuedSynchronizer類
1.3.2.Node內部類
1.3.4.state
首先說一下共享資源變量state,它是int數據類型的,其訪問方式有3種:
- getState()
- setState(int newState)
- compareAndSetState(int expect, int update)
重入鎖計數/許可證數量,在不同的鎖中,使用方式有所不同
上述3種方式均是原子操作,其中compareAndSetState()的實現依賴於Unsafe的compareAndSwapInt()方法。
AQS維護了一個volatile語義(支持多線程下的可見性)的共享資源變量state和一個FIFO線程等待隊列(多線程競爭state被阻塞時會進入此隊列)。
// 重入鎖計數/許可證數量,在不同的鎖中,使用方式有所不同
private volatile int state;
// 具有內存讀可見性語義
protected final int getState() {
return state;
}
// 具有內存寫可見性語義
protected final void setState(int newState) {
state = newState;
}
// 具有內存讀/寫可見性語義
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS中的int類型的state值,各種鎖就是通過CAS(樂觀鎖)去修改state的值。lock的基本操作還是通過樂觀鎖來實現的。
1.3.5.CLH隊列(FIFO)
AQS是通過內部類Node來實現FIFO隊列的,源代碼解析如下:
static final class Node {
// 表明節點在共享模式下等待的標記
static final Node SHARED = new Node();
// 表明節點在獨佔模式下等待的標記
static final Node EXCLUSIVE = null;
// 表徵等待線程已取消的
static final int CANCELLED = 1;
// 表徵需要喚醒後續線程
static final int SIGNAL = -1;
// 表徵線程正在等待觸發條件(condition)
static final int CONDITION = -2;
// 表徵下一個acquireShared應無條件傳播
static final int PROPAGATE = -3;
/**
* SIGNAL: 當前節點釋放state或者取消後,將通知後續節點競爭state。
* CANCELLED: 線程因timeout和interrupt而放棄競爭state,當前節點將與state徹底拜拜
* CONDITION: 表徵當前節點處於條件隊列中,它將不能用作同步隊列節點,直到其waitStatus被重置爲0
* PROPAGATE: 表徵下一個acquireShared應無條件傳播
* 0: None of the above
*/
volatile int waitStatus;
// 前繼節點
volatile Node prev;
// 後繼節點
volatile Node next;
// 持有的線程
volatile Thread thread;
// 鏈接下一個等待條件觸發的節點
Node nextWaiter;
// 返回節點是否處於Shared狀態下
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回前繼節點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// Shared模式下的Node構造函數
Node() {
}
// 用於addWaiter
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
// 用於Condition
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
private transient volatile Node head; // 【|同步隊列|】的頭結點
private transient volatile Node tail; // 【|同步隊列|】的尾結點
// 前繼節點
volatile Node prev;
// 後繼節點
volatile Node next;
最後我們可以發現鎖的存儲結構就兩個東西:“雙向鏈表” + “state(鎖狀態)”。
需要注意的是,他們的變量都被"transient
和volatile
修飾。
還可以看到,waitStatus非負的時候,表徵不可用,正數代表處於等待狀態,所以waitStatus只需要檢查其正負符號即可,不用太多關注特定值。
1.3.6.資源的共享方式分爲2種(後面細講)
獨佔式(Exclusive)
只有單個線程能夠成功獲取資源並執行,如ReentrantLock。
獲取資源
- public final void acquire(int arg) 申請獨佔鎖,允許阻塞帶有中斷標記的線程(會先將其標記清除)
釋放資源
- public final boolean release(int arg) 釋放鎖,如果鎖已被完全釋放,則喚醒後續的阻塞線程。返回值表示本次操作後鎖是否自由
共享式(Shared)
多個線程可成功獲取資源並執行,如Semaphore/CountDownLatch等。
獲取資源
- public final void acquireShared(int arg) 申請共享鎖,若獲取成功則直接返回,若失敗,則進入等待隊列,執行自旋獲取資源。
釋放資源
- public final boolean releaseShared(int arg) 釋放鎖,並喚醒排隊的結點。
AQS需要子類複寫的方法均沒有聲明爲abstract,目的是避免子類需要強制性覆寫多個方法,因爲一般自定義同步器要麼是獨佔方法,要麼是共享方法,只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。
當然,AQS也支持子類同時實現獨佔和共享兩種模式,如ReentrantReadWriteLock。
AQS在獨佔和共享兩種模式下,在acquire()和acquireShared()方法中,線程在阻塞過程中均是忽略中斷的。
2.總結
AQS指的是AbstractQueuedSynchronizer
J.U.C是基於AQS實現的,AQS是一個同步器,設計模式是模板模式。
核心數據結構:雙向鏈表 + state(鎖狀態)
底層操作:CAS
3.參考
非常感謝康建偉分享的jdk源碼筆記
https://github.com/kangjianwei/LearningJDK
淺談Java的AQS:
https://www.jianshu.com/p/0f876ead2846