Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

從源碼學習Java併發的鎖是怎麼維護內部線程隊列的

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

本篇是《凱哥(凱哥Java:kagejava)併發編程學習》系列之《Lock系列》教程的第一篇:《Java併發包下鎖學習第三篇-從源碼學習Java併發是怎麼維護內部線程隊列的》。

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

一:Node節點對象介紹

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

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

簡化後:

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

對象中屬性介紹

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:當前獲取到同步狀態的線程對象。

具體可以如下圖:

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

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

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

二:維護線程排隊的流程圖

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

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

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

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

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

流程說明:

入隊列

入隊流程如下:

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

上圖流程說明:

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

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


出隊列

出隊流程圖如下:

Java併發包下鎖學習第三篇-鎖是怎麼維護內部隊列的

出隊流程說明:

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


分享到:


相關文章: