[英]What is the purpose of fairness parameter in REENTRANT LOCK in JAVA?
我在瀏覽可重入鎖的 Java 文檔時發現了以下文本:
鎖的公平性並不能保證線程調度的公平性。 因此,使用公平鎖的許多線程之一可能會連續多次獲得它,而其他活動線程沒有進展並且當前沒有持有鎖。
根據我的理解,這意味着,如果操作系統調度程序調度相同的線程(之前獲取鎖)並嘗試再次獲取相同的鎖,Java 將允許它獲取並且不會遵守公平參數值。 有人可以告訴我們公平參數的目的是什么,以及應該在什么條件下使用它。
我只是在想它是否就像一個優先級值,這可能會影響調度程序但不能保證線程執行順序。
鎖的公平性並不能保證線程調度的公平性。 因此,使用公平鎖的許多線程之一可能會連續多次獲得它,而其他活動線程沒有進展並且當前沒有持有鎖。
我將“沒有進展”解釋為“由於與所討論的鎖無關的原因而沒有進展。 ”我認為他們試圖告訴你,“公平”僅在鎖受到如此激烈的競爭以致經常存在時才有意義。一個或多個線程等待輪到它們鎖定它。
如果線程 T 釋放當前沒有其他線程正在等待的“公平”鎖,則“公平”不會影響下一個線程將獲得它。 這只是線程之間的直接競賽,由操作系統調度程序主持。
只有當多個線程正在等待時,一個公平的鎖才應該“支持”等待時間最長的那個。 特別是,我希望如果某個線程 T 釋放其他線程正在等待的“公平”鎖,然后線程 T立即嘗試再次鎖定它,那么lock()
function 會注意到其他等待線程,並發送 T到隊列的后面。
但是,我實際上並不知道它是如何在任何特定的 JVM 中實現的。
PS,IMO,“公平”就像綳帶,用於止血復合骨折。 如果您的程序有一個競爭激烈的鎖,以至於“公平”會產生任何影響,那么這是一個嚴重的設計缺陷。
同一個 Javadoc還說,
使用由許多線程訪問的公平鎖的程序可能會顯示出比使用默認設置的程序更低的整體吞吐量(即,速度較慢;通常要慢得多)。
在幼稚的觀點中,使用公平鎖的線程的行為就像
線程 1 | 線程 2 | 線程 3 |
---|---|---|
獲得 | 做一點事 | 做一點事 |
臨界區 | 嘗試獲取 | 做一點事 |
臨界區 | 被封鎖 | 嘗試獲取 |
發布 | 獲得 | 被封鎖 |
做一點事 | 臨界區 | 被封鎖 |
嘗試獲取 | 發布 | 獲得 |
被封鎖 | 做一點事 | 臨界區 |
獲得 | 做一點事 | 發布 |
“Try Acquire”是指調用lock()
不會立即成功,因為另一個線程擁有鎖。 它沒有引用tryLock()
,這通常是不公平的。
在這種幼稚的觀點中,線程按“線程 1”、“線程 2”、“線程 3”的順序獲取鎖,因為這是獲取嘗試的順序。 尤其是當“線程 1”試圖在“線程 2”釋放它的同時獲取鎖時,它不會像不公平鎖那樣超過,而是“線程 3”得到它,因為它等待的時間更長。
但是,正如文檔所說,線程調度是不公平的。 因此,可能會發生以下情況。
線程 1 | 線程 2 | 線程 3 |
---|---|---|
獲得 | 做一點事 | 做一點事 |
臨界區 | 做一點事 | |
臨界區 | ||
發布 | ||
做一點事 | ||
獲得 | 嘗試獲取 | 嘗試獲取 |
臨界區 | 被封鎖 | 被封鎖 |
臨界區 | 被封鎖 | 被封鎖 |
空單元格表示線程根本沒有獲得任何 CPU 時間的階段。 線程可能比 CPU 內核多,其中包括其他進程的線程。 操作系統甚至可能更喜歡讓“線程 1”在一個核心上繼續運行,而不是切換到其他線程,這僅僅是因為該線程確實已經運行並且切換需要時間。
通常,嘗試預測到達某個點的相對時間(例如前面的工作負載獲取鎖)並不是一個好主意。 在具有優化 JIT 編譯器的環境中,即使兩個線程使用完全相同的輸入執行完全相同的代碼也可能具有完全不同的執行時間。
因此,當我們無法預測lock()
嘗試的時間時,堅持以不可預測的未知順序獲取鎖並不是很有用。 開發人員仍然希望公平的一種解釋是,即使結果順序不可預測,它也應該確保每個線程都取得進展,而不是在其他線程反復超車時無限等待鎖。 但這讓我們回到了不公平的線程調度; 即使根本沒有鎖,也不能保證所有線程都能取得進展。
那么為什么公平選項仍然存在呢? 因為有時,人們對它在大多數情況下的工作方式感到滿意,即使沒有強有力的保證它會一直以這種方式工作。 或者簡單地說,因為如果它不存在,開發人員會反復要求它。 支持公平的成本並不高,也不會影響不公平鎖的性能。
ReentrantLock
是基於AbstractQueuedSynchronizer
實現的,它是一個先進先出 (FIFO) 等待隊列。
假設三個線程A、B、C依次嘗試獲取鎖,A獲取了鎖,那么B、C會轉化為AbstractQueuedSynchronizer#Node
進入隊列。 這兩個線程將被掛起。
當A線程釋放鎖時,它會喚醒它的后繼節點( AbstractQueuedSynchronizer#unparkSuccessor
),即線程B。線程B在被喚醒后會再次嘗試獲取鎖。
假設當B線程被喚醒時,突然有一個D線程來嘗試獲取這個鎖。 對於公平鎖,D線程看到隊列中有其他節點在等待獲取鎖( AbstractQueuedSynchronizer#hasQueuedPredecessors
),直接掛掉。
而對於不公平鎖,D線程會立即嘗試獲取這個鎖,這意味着它可以嘗試“跳隊列”一次。 如果這次“隊列跳轉”成功,那么可以立即獲取鎖(這意味着節點B將再次被掛起:它在與D線程的競爭中輸了,它被切線了)。 如果失敗,它將被掛起並作為節點進入隊列。
為什么不公平鎖表現更好以及何時使用公平鎖?
這是來自Java-Concurrency-Practice :
在激烈的爭用下,插入鎖比公平鎖執行得更好的一個原因是,在暫停的線程恢復和實際運行之間可能存在顯着延遲。 假設線程 A 持有一個鎖,而線程 B 請求該鎖。 由於鎖忙,B被掛起。 當 A 釋放鎖時,B 會恢復,以便再次嘗試。 與此同時,如果線程 C 請求鎖,那么 C 很有可能在 B 完成喚醒之前獲取鎖,使用它並釋放它。 在這種情況下,每個人都贏了:B 獲得鎖的時間不會晚於其他情況,C 獲得鎖的時間要早得多,從而提高了吞吐量。
當公平鎖被持有的時間相對較長或鎖請求之間的平均時間相對較長時,它們往往工作得最好。 在這些情況下,插入提供吞吐量優勢的條件 - 當鎖未持有但線程當前正在喚醒以聲明它時 - 不太可能持有。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.