並發之AQS原理(二) CLH隊列與Node解析
1.CLH隊列與Node節點
就像通常醫院看病排隊一樣,醫生一次能看的病人數量有限,那么超出醫生看病速度之外的病人就要排隊。
一條隊列是隊列中每一個人的組織形式。那么每個人決定怎么看待自己在隊列中的形態決定了整個隊列的形態。比如當每個人都遵守先來后到的原則時,那么最先來的人會站到第一個,之后每個人都會順序排開。
同樣的隊列這個類不存在,讓他們形成隊列的是每個節點類的組織形式。所以想分析隊列就必須要先分析節點。
所謂的CLH隊列本質上就是一個雙向鏈表Node就是該鏈表的節點。當然CLH隊列並不是簡單的雙向鏈表
上圖直觀的向我們展示了節點的組織狀態,我們可以看看node節點的源代碼。
2.node節點屬性的解析
node節點作為CLH隊列的一個節點,有着5條屬性,分別是waitStatus 、prev、next、thread、nextWater。下面我們將一一解析這五種屬性的作用。
waitStatus介紹
waitStatus是當前節點的一個等待狀態標志位,該標志位決定了該節點在當前情況下處於何種狀態。
不用再說了,直接看注釋吧。這里我們說下Node。Node結點是對每一個訪問同步代碼的線程的封裝,其包含了需要同步的線程本身以及線程的狀態,如是否被阻塞,是否等待喚醒,是否已經被取消等。變量waitStatus則表示當前被封裝成Node結點的等待狀態,共有4種取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。
CANCELLED:值為1,在同步隊列中等待的線程等待超時或被中斷,需要從同步隊列中取消該Node的結點,其結點的waitStatus為CANCELLED,即結束狀態,進入該狀態后的結點將不會再變化。
SIGNAL:值為-1,被標識為該等待喚醒狀態的后繼結點,當其前繼結點的線程釋放了同步鎖或被取消,將會通知該后繼結點的線程執行。說白了,就是處於喚醒狀態,只要前繼結點釋放鎖,就會通知標識為SIGNAL狀態的后繼結點的線程執行。
CONDITION:值為-2,與Condition相關,該標識的結點處於等待隊列中,結點的線程等待在Condition上,當其他線程調用了Condition的signal()方法后,CONDITION狀態的結點將從等待隊列轉移到同步隊列中,等待獲取同步鎖。
PROPAGATE:值為-3,與共享模式相關,在共享模式中,該狀態標識結點的線程處於可運行狀態。
AQS運用該屬性時的狀態判斷
狀態 | 判斷結果 | 說明 |
---|---|---|
waitStatus=0 | 代表初始化狀態 | 該節點尚未被初始化完成 |
waitStatus>0 | 取消狀態 | 說明該線程中斷或者等待超時,需要移除該線程 |
waitStatus<0 | 有效狀態 | 該線程處於可以被喚醒的狀態 |
prve next thread介紹
prve 是同步線程隊列中保存的前置節點的地址。
next 是同步線程隊列中保存的后續節點的地址。
thread 同步線程隊列主要存儲的線程信息。
nextWaiter介紹
AQS中阻塞隊列采用的是用雙向鏈表保存,用prve和next相互鏈接。而AQS中條件隊列是使用單向列表保存的,用
nextWaiter來連接。阻塞隊列和條件隊列並不是使用的相同的數據結構。
在Node節點的源碼中有兩個常量屬性
// 共享模式
static final Node SHARED = new Node();
// 獨占模式
static final Node EXCLUSIVE = null;
// 其他模式
// 其他非空值:條件等待節點(調用Condition的await方法的時候)
nextWaiter實際上標記的就是在該節點喚醒后依據該節點的狀態判斷是否依據條件喚醒下一個節點。
nextWaiter狀態標志 | 說明 |
---|---|
SHARED(共享模式) | 直接喚醒下一個節點 |
EXCLUSIVE(獨占模式) | 等待當前線程執行完成后再喚醒 |
其他非空值 | 依據條件決定怎么喚醒下一個線程。類似semaphore中控制幾個線程通過 |
node節點的屬性介紹完了,下面來介紹node節點的方法以及各個方法的用戶
3.node節點方法解析
構造方法
// 構造方法為空參構造,一般用於創建head節點,或者為nextWaiter設置共享標志。
Node() {
}
// 構造方法用於創建一個帶有條件隊列的節點
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
// 用於創建一個帶有初始等waitStatus的節點
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
isShared方法
顯而易見這個方法使用來檢查當前節點是否為共享節點。
final boolean isShared() {
return nextWaiter == SHARED;
}
predecessor方法
該方法用來查找前置節點是否存在,相當於為前置節點查空。
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
4.基於Node的的CLH阻塞隊列是如何運作的
首先 CLH隊列鎖通常使用自旋鎖來阻塞線程執行,使用本節點和前置節點的waitStatus來判斷線程是否阻塞。在前置節點獲取執行權限的時候發出信號。每個節點都有一個單獨等待通知的監視器,waitStatus不會控制線程是否獲取到了鎖。獲取鎖的過程是通過查看隊列中的第一個node中的waitStatus是否處於可以執行的狀態。如果可執行則繼續執行,線程被中斷或者超時了就尋找后續node.
CLH鎖出列只設置更新頭部節點,插入隊列只需要原子更新尾部的節點。
首先確定自己是否為頭部節點,如果是頭部節點則直接獲取資源開始執行,如果不是則自旋前置節點直到前置節點執行完成狀態修改為CANCELLED,然后斷開前置節點的鏈接,獲取資源開始執行。
這部分操作的具體詳情會在后續的系列中詳細講解。
5.總結
CLH阻塞隊列采用的是雙向鏈表隊列,頭部節點默認獲取資源獲得執行權限。后續節點不斷自旋方式查詢前置節點是否執行完成,直到頭部節點執行完成將自己的waitStatus狀態修改以通知后續節點可以獲取資源執行。CLH鎖是一個有序的無飢餓的公平鎖。