3 回答

TA貢獻1906條經驗 獲得超3個贊
那么在我的理解中,如果我有:
int xx = x; // read x int yy = y; // read y這些讀數可以重新排序。
這些讀取可能不僅碰巧被重新排序,它們可能根本不會發生。該線程可能使用舊的、以前讀取的值x
和/或y
它之前寫入這些變量的值,而實際上,寫入可能尚未執行,因此“讀取線程”可能使用值,而不是其他線程當時可能知道并且不在堆內存中(并且可能永遠不會)。
另一方面,如果我有:
// simplified code, does not compile, but reads happen on the same "this" for example int xx = VarHandle_X.getOpaque(x); int yy = VarHandle_Y.getOpaque(y);這次不能重新訂貨了?這就是“程序順序”的意思?
簡單地說,不透明讀寫的主要特征是,它們會實際發生。這意味著它們不能相對于至少具有相同強度的其他內存訪問重新排序,但這對普通讀寫沒有影響。
術語程序順序由 JLS 定義:
… t的程序順序是一個總順序,它反映了根據t的線程內語義執行這些操作的順序。
這是為表達式和語句指定的評估順序。我們感知效果的順序,只要只涉及一個線程。
我們是在談論在這里插入障礙以禁止這種重新排序嗎?
不,不涉及障礙,這可能是短語“ ......但不能保證相對于其他線程的內存排序效果”背后的意圖。
或許,我們可以說不透明訪問的工作方式有點像volatile
Java 5 之前的版本,強制讀取訪問以查看最近的堆內存值(這只有在寫入端也使用不透明或更強大的模式時才有意義),但沒有對其他讀取或寫入的影響。
那么你能用它做什么呢?
一個典型的用例是一個取消或中斷標志,它不應該建立一個happens-before關系。通常,已停止的后臺任務沒有興趣感知停止任務在發出信號之前所做的操作,而只會結束自己的活動。因此,使用不透明模式寫入和讀取標志足以確保最終注意到信號(與正常訪問模式不同),但不會對性能產生任何額外的負面影響。
同樣,后臺任務可以寫入進度更新,如百分比數字,報告 (UI) 線程應該及時注意到,而在最終結果發布之前不需要happens-before關系。
如果您只想對 和 進行原子訪問,而沒有任何其他影響,它也很有long
用double
。
由于使用字段的真正不可變對象final
不受數據競爭的影響,因此您可以使用不透明模式及時發布不可變對象,而不會產生發布/獲取模式發布的更廣泛影響。
一種特殊情況是定期檢查預期值更新的狀態,一旦可用,就以更強的模式查詢該值(或顯式執行匹配的圍欄指令)。原則上,無論如何只能在寫入和后續讀取之間建立happens-before關系,但由于優化器通常沒有識別此類線程間用例的視野,因此性能關鍵代碼可以使用不透明訪問來優化這樣的場景。

TA貢獻1895條經驗 獲得超7個贊
不透明意味著執行不透明操作的線程保證按照程序順序觀察自己的動作,僅此而已。
其他線程可以自由地以任何順序觀察線程的動作。在 x86 上這是一個常見的情況,因為它有
使用存儲緩沖區轉發命令寫入
內存模型,因此即使線程在加載之前確實存儲。存儲可以緩存在存儲緩沖區中,并且在任何其他內核上執行的某些線程以相反的順序加載存儲而不是存儲加載來觀察線程操作。所以不透明操作是在 x86 上免費完成的(在 x86 上我們實際上也有免費獲取,有關其他一些架構及其內存模型的詳細信息,請參閱這個極其詳盡的答案:https ://stackoverflow.com/a/55741922/8990329 )
為什么有用?好吧,我可以推測,如果某個線程觀察到一個存儲在不透明內存語義中的值,那么后續讀取將觀察到“至少這個或以后”的值(普通內存訪問不提供這樣的保證,是嗎?)。
此外,由于 Java 9 VarHandles 在某種程度上與 CI 中的獲取/釋放/消費語義相關,因此值得注意的是,不透明訪問類似于memory_order_relaxed
標準中定義的內容,如下所示:
對于
memory_order_relaxed
,沒有操作命令內存。

TA貢獻1796條經驗 獲得超10個贊
我自己一直在與不透明作斗爭,文檔當然不容易理解。
從上面的鏈接:
不透明操作是按位原子和連貫有序的。
按位原子部分是顯而易見的。一致排序意味著加載/存儲到單個地址有一些總順序,每個到達看到它之前的最近地址并且順序與程序順序一致。有關一些連貫性示例,請參閱以下JCStress 測試。
Coherence 不在不同地址的加載/存儲之間提供任何排序保證,因此它不需要提供任何柵欄以便對不同地址的加載/存儲進行排序。
使用不透明,編譯器將發出它看到的加載/存儲。但是仍然允許底層硬件將加載/存儲重新排序到不同的地址。
我將您的示例升級為消息傳遞石蕊測試:
thread1:
X.setOpaque(1);
Y.setOpaque(1);
thread2:
ry = Y.getOpaque();
rx = X.getOpaque();
if (ry == 1 && rx == 0) println("Oh shit");
在允許重新排序 2 個存儲或 2 個加載(同樣是 ARM 或 PowerPC)的平臺上,上述操作可能會失敗。不透明不需要提供因果關系。JCStress 也有一個很好的例子。
此外,以下 IRIW 示例可能會失?。?/p>
thread1:
X.setOpaque(1);
thread2:
Y.setOpaque(1);
thread3:
rx_thread3 = X.getOpaque();
[LoadLoad]
ry_thread3 = Y.getOpaque();
thread4:
ry_thread4 = Y.getOpaque();
[LoadLoad]
rx_thread4 = X.getOpaque();
難道我們最終得到 rx_thread3=1,ry_thread3=0,ry_thread4=1 而 rx_thread4 為 0 嗎?
對于不透明,這可能會發生。即使負載被阻止重新排序,不透明訪問也不需要多副本原子性(可以看到不同 CPU 發出的不同地址的存儲以不同的順序)。
Release/acquire 比 opaque 強,因為 release/acquire 允許失敗,因此 opaque 允許失敗。所以 Opaque 不需要提供共識。
添加回答
舉報