我知道下一個場景:(奇怪的格式,我知道)
private final AtomicBoolean aBoolean = new AtomicBoolean(true);
public void doSomething() {
if (
aBoolean.get() // line A
&& // line B
aBoolean.compareAndSet(true, false) // line C
) {
System.out.println("Was true!")
}
}
如果執行緒#1 和執行緒#2doSomething()恰好同時進入,這將發生:
執行緒 #1 和執行緒 #2 將
aBoolean.get()同時讀取為 == "true"。兩者都將執行“&&”運算子。
CMPXCHG 指令同時為兩個執行緒啟動:
3.1 本地使用LOCK前綴
3.2 執行緒#1 或#2 先到達,贏得比賽。
3.3 獲勝執行緒比較(aBoolean == true 嗎?)這將回傳“true”,因此 aBoolean 將設定為“false”。
3.4 aBoolean 現在是假的。
3.5 丟失執行緒比較(是 aBoolean == true 嗎?)這將回傳“false”,因此會短路任何進一步的操作。
獲勝執行緒將列印“是真的!”。
在“失敗”執行緒的角度下,“ aBoolean.get()A 線”中的第一個是......假設......一個“謊言”。
現在假設執行可以發生在運算子之間,就像上面例子中所做的那樣,讓我們??為第二種情況添加第二種方法:
public void unluckySet() {
aBoolean.set(false);
}
假設執行緒#3恰好unluckySet()在我們的“獲勝執行緒”到達“&&”正在執行的“B行”的確切時刻到達并執行。
如果獲勝執行緒到達“B 行”,則意味著它到達“A 行”,aBoolean 為“真”。
我的問題是:
CMPXCHG 是否會將更新后的值正確讀取為“假”?這意味著.set()也由與 . 相同的鎖持有compareAndSet()。
在并發和執行緒之間:
運算子(“&&”、“||”、“==”、“=”,甚至可能是“return;”?以交錯方式結束,防止可能的沖突?
uj5u.com熱心網友回復:
Java 記憶體模型在沒有資料競爭(您的程式沒有)的情況下是順序一致性的。這是非常強大的;它說你程式中的所有讀寫形成一個總順序,這與程式順序一致。因此,您可以想象來自不同執行緒的讀取和寫入只是以某種方式交錯或混洗在一起 - 而不會改變從同一執行緒執行的操作的相對順序。
但是為了這個目的,每個動作都是這個順序中的一個單獨的元素。因此,僅僅因為aBoolean.get()andaBoolean.compareAndSet()是兩個動作而不是一個動作,其他執行緒的任意數量的其他動作都可能發生在它們之間。
這些動作是單個陳述句的一部分還是不同陳述句的一部分都沒有關系;或者他們以什么樣的表情出現;或者他們之間有什么運營商(如果有的話);或者執行緒本身可能會或可能不會圍繞它們進行哪些計算。沒有辦法讓兩個動作“如此接近”,以至于在兩者之間不會發生任何其他事情,除非用語言定義為原子的單個動作替換它們。
在機器級別,一個非常簡單的方法是,因為aBoolean.get()幾乎aBoolean.compareAndSet()可以肯定是兩條不同的機器指令,中斷可能會到達它們之間。這個中斷可能導致執行緒被延遲任意時間,在此期間其他執行緒可以做任何他們想做的事情。因此,執行緒#1 和#2 完全有可能在它們的get()and之間都被中斷compareAndSet(),而執行緒#3set在此期間執行它。
注意:推理特定機器可能如何作業通常有助于理解為什么可能出現不希望的行為,如上一段所述。但它不能替代對正式記憶體模型的推理,也不應該用來試圖爭論一個程式必須有它想要的行為。即使你心目中的一臺特定機器會為你的代碼做正確的事情,或者你想不出一個看似合理的機器會失敗的方式,但這并不能證明你的程式是正確的。
所以試圖說“哦,機器會做lock cmpxchg等等等等等等等等,一切正常”是不明智的。您沒有想到的其他一些機器可能以完全不同的方式作業,它仍然符合抽象 Java 記憶體模型,但在其他方面違反了您基于 x86 的期望。事實上,x86 是一個特別糟糕的例子:由于歷史原因,它提供了一組相當強大的指令級記憶體排序保證,而許多其他“弱排序”架構則沒有,因此可能有很多東西是 Java 抽象的允許,但實際上 x86 不會這樣做。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/417310.html
標籤:
