那什么是管程呢?所謂管程,就是 管理共享變量以及對共享變量操作的過程 ,其有三種模型,分別為 Hasen 模型、Hoare 模型和 MESA 模型。目前應用最廣泛的是MESA模型,而JAVA采用的也是這種MESA模型(其模型圖如下圖所示):
可能這個圖大家現在還看不太明白,沒關系,暫時留個印象,當看完指北君AQS系列文章以后,你再回過頭來看這個圖,肯定秒懂!
Java中的synchronized關鍵字就是其管程的具體實現,當然,今天所要聊的AQS同樣也是。AQS是Java并發編程的基礎,只要掌握它,java.util .concurrent工具包下的大部分工具類源碼你都能在10分鐘內看懂,連源碼都懂了,還怕不知道怎么用嗎?!
所以,針對AQS,指北君將用三篇文章來講解:
第一篇即本篇,我會將AQS做個整體的介紹,并將其基礎總結成了三把斧頭,有了這三把斧頭,你就能快速斬獲AQS
第二篇我們則講AQS如何通過鎖機制解決互斥問題
第三篇則是AQS如何通過條件變量來解決線程通信協作問題
好了,現在隨著指北君開始第一篇的學習吧
AQS是什么
好了,本文的正篇正式開始。前言說了半天AQS,AQS到底是什么呢?AQS全稱為AbstractQueuedSynchronizer,翻譯成中文即:抽象隊列同步器,它是java.util .concurrent工具包下的抽象類,也是一個模板類(設計模式中的模板模式可以了解一波),你也可以理解為為開發人員提供的一種同步框架,它已經幫我們實現了大部分公共通用邏輯,如線程入隊、出隊,阻塞、喚醒等,我們只需要根據我們自己的需求,實現一些特定的方法,這些方法也叫鉤子方法,比如下面幾個方法:
- tryAcquire:獨占模式下獲取同步狀態
- tryRelease:獨占模式下釋放同步狀態
- tryAcquireShared:共享模式下獲取同步狀態
- tryReleaseShared:共享模式下釋放同步狀態
- isHeldExclusively:獨占模式下,查看當前線程是否獲取同步狀態
AQS針對互斥,提供了兩種模式,即獨占模式和共享模式。獨占模式只允許一個線程拿到鎖去操作共享資源,而共享鎖則有多把鎖,允許多個線程同時操作共享資源,這個第二篇會詳細講解,在這之前我們需要先了解AQS的三板斧,這三個是了解AQS的基礎:
- 狀態:AQS中的所有邏輯都是依據狀態state來進行的,所以它是整個類的和興。它是被關鍵字volatile修飾的,保證其可見性和部分有序性
- 隊列:一共有兩種隊列,同步隊列和條件隊列。當線程的請求在短時間內得不到滿足時,線程會被包裝成某種類型的數據結構放入隊列中,當條件滿足時則會拿出隊列。
- CAS:由Unsafe工具類來實現的,其操作具有原子性,AQS通過CAS和volatile來保證狀態的線程安全
第一板斧:狀態
private volatile int state;
狀態是被volatile修飾的int類型變量,它的值代表著鎖還剩多少。在獨占鎖模式下,只有一把鎖,則state只有0和1兩個值,1代表鎖沒被其他線程占有,目前可獲取;0則代表鎖已經被其他線程占有了。在共享鎖模式下,state最大值就是鎖的數量。
后面我們對state的修改都是通過CAS操作,所以是線程安全的。
第二板斧:隊列
AQS中存在兩種隊列,一種叫同步隊列,它是雙向鏈表,其獲取鎖失敗的線程會被包裝成Node放入同步隊列中;另一種叫條件隊列,它是單向鏈表,線程可以調用等待方法來把自己放入條件隊列,或者調用喚醒方法把條件隊列的其他被包裝成Node的線程移到同步隊列中。這個大家如果此時看不太懂沒關系,第三篇文章會詳細介紹這個等待喚醒機制。我們來看看線程包裝成Node的Node類:
static final class Node {
// nextWaiter屬性的具體值
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 鎖的狀態
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 線程所處的等待鎖的狀態:CANCELLED,SIGNAL,CONDITION,PROPAGATE,初始化時,該值為0
volatile int waitStatus;
// 雙向鏈表前指針和后指針
volatile Node prev;
volatile Node next;
// 表示當前Node所代表的Thread
volatile Thread thread;
// 如果此屬性為EXCLUSIVE,則為獨占模式;為SHARED,則為共享模式
Node nextWaiter;
// 判斷是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 獲取鏈表的頭個節點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 構造函數,一般用在創建head頭結點時使用
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;
}
}
指北君已經在源碼上做了詳細的注釋,但這幾個關鍵的屬性上還是提出來單獨看看。
下面四個屬性是和節點相關的:
- prev:雙向鏈表的前驅節點
- next:雙向鏈表的后繼節點
- thread:節點所代表的線程
- waitStatus:該節點線程所處的狀態,即等待鎖的狀態
下面四個屬性是waitStatus的具體狀態:
- CANCELLED:此節點的線程被取消了
- SIGNAL:此節點的后繼節點線程被掛起,需要被喚醒
- CONDITION:此節點的線程在等待信號,也表明當前節點不在同步隊列中,而在條件隊列中
- PROPAGATE:此節點下一個acquireShared應該無條件傳播
關于waitStatus有幾個點需要注意下:
- waitStatus除了上面四個狀態,還有一個隱式的狀態為0,即在Node初始化的時候
- 在獨占鎖模式下,只會有到狀態CANCELLED和SIGNAL。需要特別注意的是,SIGNAL它代表的不是自己線程的狀態,而是它后繼節點的狀態,當一個節點waitStatus為SIGNAL時,意味著此節點的后繼節點被掛起,當此節點釋放鎖或者被取消拿鎖,應該要喚醒后繼節點
- 在共享鎖模式下,只會用到狀態CANCELLED和PROPAGATE
第三板斧:CAS
CAS又叫比較交換操作,它是Unsafe類中compareAndSwapXXX方法,我通過下面的例子做個簡單的介紹:
unsafe.compareAndSwapObject(this, tailOffset, expect, update);
CAS執行時,會拿地址tailOffset上的值與expect做比較,如果相同,則會將地址上的值更新為update,并返回true,否則直接返回false。
了解了CAS的基本例子后,我們看下AQS中CAS相關的代碼:
private static final Unsafe unsafe = Unsafe.getUnsafe();
// state、head、tail,waitStatus、next的偏移量
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
// 靜態代碼塊,初始化五個偏移量
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
// 下面5個方法都是CAS操作了
// cas操作 state
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// cas操作 head
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
// cas操作 tail
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
// cas操作 waitStatus
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
// cas操作 nextOffset
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
我們說第二板斧的時候說過,其五個屬性state、head、tail,waitStatus、next都是被volatile修飾的,所以CAS對其操作能保證其線程安全,因此也可以猜到這些屬性肯定是多線程爭著修改的目標。靜態塊里則是對這五個屬性偏移量進行初始化。
總結
AQS的三板斧就介紹完啦,我們再來簡單回顧下:
AQS(AbstractQueuedSynchronizer)是java.util .concurrent工具包下的抽象類,它通過實現MESA管程來解決并發領域中的同步與互斥問題。AQS實現中最重要的三點就是狀態、CAS和隊列,我們也稱之為AQS的三板斧。AQS的一切操作都是依據狀態state來的,它是被volatile修飾的全局變量,因此我們通過CAS操作使其線程安全。隊列是維護阻塞等待線程的容器,所有未獲得鎖或被要求等待的線程都會被包裝成Node放入隊列中。
-
源碼
+關注
關注
8文章
633瀏覽量
29140 -
容器
+關注
關注
0文章
494瀏覽量
22045 -
模型
+關注
關注
1文章
3172瀏覽量
48715
發布評論請先 登錄
相關推薦
評論