我正在閱讀一篇關于記憶體屏障及其在 JVM 并發中的作用的有趣文章,Dekker 演算法的示例實作引起了我的注意
volatile boolean intentFirst = false;
volatile boolean intentSecond = false;
volatile int turn = 0;
// code run by first thread // code run by second thread
1 intentFirst = true; intentSecond = true;
2
3 while (intentSecond) { while (intentFirst) { // volatile read
4 if (turn != 0) { if (turn != 1) { // volatile read
5 intentFirst = false; intentSecond = false;
6 while (turn != 0) {} while (turn != 1) {}
7 intentFirst = true; intentSecond = true;
8 } }
9 } }
10 criticalSection(); criticalSection();
11
12 turn = 1; turn = 0; // volatile write
13 intentFirst = false; intentSecond = false; // volatile write
文章提到,由于 volatiles 是順序一致的,臨界區必然會由一個執行緒執行,該執行緒使用happens-before 保證進行檢查。但是,如果兩個執行緒繼續在回圈中執行相同的邏輯,這仍然成立嗎?我的理解是,底層作業系統調度程式可能會在第 7 行執行之前決定在后續執行中暫停第二個執行緒并讓第一個執行緒命中臨界區,同時作業系統恢復第二個執行緒并命中臨界區節同時。我的理解是否正確,并且給出此示例的想法是此代碼僅執行一次?如果是這樣,我會假設我的問題的答案是“否”,因為 volatile 僅用于記憶體可見性保證。
uj5u.com熱心網友回復:
我的理解是,底層作業系統調度程式可能會在第 7 行執行之前決定在后續執行中暫停第二個執行緒并讓第一個執行緒命中臨界區,同時作業系統恢復第二個執行緒并命中臨界區節同時。
這不會發生。如果第二個執行緒掛起并且第一個執行緒在臨界區中,intentFirst則仍然為真,因為僅在第一個執行緒離開臨界區后才將其設定為 false。因此,如果第二個執行緒喚醒,intendSecond則設定為 true,但第二個執行緒將卡在while(intendedFirst)回圈中并延遲進入臨界區,直到第一個執行緒被激發。
恕我直言,Dekkers 演算法中關于發生之前最有趣的部分在這里:
intentSecond=true; // volatile write
while(intendFirst) // volatile read
現代處理器具有存盤緩沖區,這可能會導致舊存盤被重新排序,新加載到不同的地址。這就是為什么在較早的存盤和較晚的加載之間需要 [StoreLoad] 以確保它們順序一致的原因。
uj5u.com熱心網友回復:
編譯器確保平臺上已編譯的代碼(在這種情況下是 JVM)對易失性變數做出某些保證,包括陳述句的重新排序(在多個級別)和執行緒之間的可見性。在大多數具有 JVM 實作的平臺上,這可以在不涉及作業系統的情況下完成,并且不涉及調度。
關于重新排序和記憶體屏障的保證本身不是進入臨界區所需的排除機制。這些可以通過許多不同的方式實作,例如通過 Dekker 演算法。Dekker 演算法就是所謂的繁忙演算法,只要 CPU 不允許進入臨界區,就需要 CPU 一直作業。在現代硬體上,可以使用CAS操作的更簡單的演算法是可能的,例如
// declare an atomic boolean (package java.util.concurrent.atomic)
AtomicBoolean busyFlag = new AtomicBoolean(false);
// the part with the critical section, same for all threads
while (!busyFlag.compareAndSet(false, true)) { /* busy wait */ }
try {
criticalSection();
}
finally {
busyFlag.set(false);
}
在這種情況下,[compareAndSet][3]確保當且僅當標志為 false 時才將標志自動設定為 true,并在發生這種情況時回傳 true。
但是,就像 Dekker 演算法一樣,這仍然是一個繁忙的機制,如果您不走運,可能會花費您大量的 CPU。一synchronized節或鎖將要求作業系統掛起當前執行緒時無法獲得的鎖。但這與volatile.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/362900.html
