1. 程式人生 > >從原始碼學習Java併發的鎖是怎麼維護內部執行緒佇列的

從原始碼學習Java併發的鎖是怎麼維護內部執行緒佇列的

從原始碼學習Java併發的鎖是怎麼維護內部執行緒佇列的

在上一篇文章中,凱哥對同步元件基礎框架- AbstractQueuedSynchronizer(AQS)做了大概的介紹。我們知道AQS能夠通過內建的FIFO佇列來完成資源獲取執行緒的排隊工作。那麼AQS是怎麼來維護這個排隊工作的呢?今天我們就來扒一扒AQS原始碼。從原始碼中來看看是怎麼維護對了的。

本篇是《凱哥(凱哥Java:kagejava)併發程式設計學習》系列之《Lock系列》教程的第一篇:《Java併發包下鎖學習第三篇-從原始碼學習Java併發是怎麼維護內部執行緒佇列的》。

在上篇我們知道AQS內部有個內部類-Node物件。這個物件就是來維護執行緒對資源訪問的排隊工作的。具體怎麼操作的呢?本文主要內容:Node節點介紹;在同步器中怎麼為維護排隊的流程圖。

一:Node節點物件介紹

在AQS內部有個Node物件的內部類。我們來看看這個物件都有哪些屬性:

 

簡化後:

static final class Node {
//執行緒等待狀態
volatile int waitStatus;
//當前節點的上一個節點
volatile Node prev;
//當前節點物件
volatile Node next;
//當前節點維護的執行緒物件
volatile Thread thread;
//當前節點的下一個(後續)節點
Node nextWaiter;
}


物件中屬性介紹

Int waitStatus:

物件裡面有表示狀態的4個屬性:

static final int CANCELLED = 1:執行緒從同步佇列中取消

static final int SIGNAL = -1:後續節點等待狀態。當前節點在獲取到資源後,在釋放前需要斷開和後續節點的連線。在其釋放後,會通知後續節點,使後續解決繼續執行。

static final int CONDITION = -2:當前節點等待中。在等待condition通知。也可以理解成在condition佇列中。

static final int PROPAGATE = -3:在共享模式下,下一次無條件傳播

0:預設狀態。

Node prev:當前節點的上一個節點

Node Next:當前解決的後續節點

Node nextWaiter:可以理解為節點的型別。是共享式還是獨佔式。

Thread thread:當前獲取到同步狀態的執行緒物件。

具體可以如下圖:

 

首先,我們需要明白,在資料結構中,能夠保持FIFO的結構是佇列模式的。但是佇列有單項佇列和迴圈佇列兩種。那麼,同步器使用的是哪個佇列方式呢?

從Node節點屬性中,我們可以看到前節點和後續節點的屬性。說明使用的是迴圈佇列。

二:維護執行緒排隊的流程圖

為了保證執行緒的安全,同步器提供了幾個CAS的方法。如下圖:

 

CAS設定頭節點、設定下一個節點、設定狀態、設定尾節點等。

操作流程可以簡述如下圖:

 

流程說明:

入佇列

入隊流程如下:

 

上圖流程說明:

當多個執行緒同時來爭奪資源的時候,其中一個執行緒獲取到了資源(同步狀態或者是鎖),這個時候獲取到資源的執行緒就會被構造成頭節點。其他線無非獲取到資源的執行緒會被構成成Node節點物件並被放到佇列中。被構造成Node節點的執行緒會排在佇列尾部排隊。為了保證執行緒安全性,同步器會基於CAS設定尾節點的方法(即:compareAndSetTail ())來保持執行緒安全性.這個方法需要傳遞當前執行緒“自己認為”的尾節點和前一個節點,當CAS執行成功之後,當前節點才會正式與之前的節點建立關係。被設定尾部的Node節點的next將指向頭節點。

如上圖中執行緒3會和執行緒1執行類似的操作,把自己新增到佇列的尾部。這樣就形成了一個完整的雙向佇列排隊了。

出佇列

出隊流程圖如下:

 

出隊流程說明:

從入隊流程圖中我們可以看出,所有爭奪資源併發的執行緒都被排隊了。同步佇列遵循FIFO(先進先出)。所謂的首節點就是獲取同步狀態成功節點。當來的首節點中的執行緒在釋放同步狀態的時候,會斷開自己與後續節點的關聯關係,然後會喚醒後續節點操作的。當後續節點獲取同步狀態成功的時候,就將自己設定為首節點,原來的首節點就退出了佇列。如果原來的首節點還需要獲取的話,後將自己執行緒構造成Node節點物件,然後進行排隊。

&n