2010-04-05 63 views
8

java meomry模型要求synchronize在同一個監視器上同步的塊在這些塊內修改的變量之前強制執行之前的事後處理。例如:Java內存模型:重新排序和併發鎖定

// in thread A 
synchronized(lock) 
{ 
    x = true; 
} 

// in thread B 
synchronized(lock) 
{ 
    System.out.println(x); 
} 

在這種情況下,它保證下,該線程B將看到x==true只要線程A已經通過了​​- 嵌段。現在我正在重寫大量代碼,以便使用java.util.concurrent中更靈活(並且更快)的鎖,特別是ReentrantReadWriteLock。因此,本例看起來像:

編輯:這個例子被打破了,因爲我錯誤地改變了代碼,由亞光指出b。修正如下:

// in thread A 
lock.writeLock().lock(); 
{ 
    x = true; 
} 
lock.writeLock().unlock(); 

// in thread B 
lock.readLock().lock(); 
{ 
    System.out.println(x); 
} 
lock.readLock().unlock(); 

不過,我還沒有看到內存的型號規格內的任何暗示,這種鎖也意味着nessessary排序。縱觀實現,它似乎依賴於訪問AbstractQueuedSynchronizer內部的易變變量(至少用於太陽實現)。然而,這不是任何規範的一部分,而且對這些變量給出的內存屏障並沒有真正考慮對非易失性變量的訪問,是嗎?

所以,這裏是我的問題:

  • 它是安全的假設相同的順序與「老」​​塊?
  • 這是否記錄在某處?
  • 正在訪問任何易變變量的內存屏障的任何其他變量?

問候, 斯特芬

-

註釋Yanamon:

請看下面的代碼:

// in thread a 
x = 1; 
synchronized (a) { y = 2; } 
z = 3; 

// in thread b 
System.out.println(x); 
synchronized (a) { System.out.println(y); } 
System.out.println(z); 

從我的理解,記憶障礙強制執行第二個輸出顯示2,但對其他變量沒有保證影響...?那麼如何才能比較訪問一個易變的變量?

+0

關於你添加的代碼的一個說明,線程b將只打印2,如果它獲得了線程之前的鎖定...這是暗示的,但我只是想說清楚。 但是要回答您不穩定的問題,請按以下方式使用volatile來強制執行可見性: -------- volatile boolean memoryBarrier = false; int unguardedValue = 0; //線程a: unguardedValue = 10; memoryBarrier = true; //線程b 如果(memoryBarrier){unguardedValue被保證讀爲10; } – Yanamon 2010-04-05 01:56:23

+0

嗯,我認爲在評論中編寫代碼並不是很好,我用一個示例更新了我的答案 – Yanamon 2010-04-05 02:07:31

回答

5

API-doc

所有Lock實現必須執行 同一存儲器同步 語義由內置 監視器鎖定,如在The Java語言規範所描述的,第三 版提供( 17.4內存模型):

* A successful lock operation has the same memory synchronization effects as a successful Lock action. 
* A successful unlock operation has the same memory synchronization effects as a successful Unlock action. 

不成功鎖定和解鎖 操作,並且可重入 鎖定/解鎖操作,請勿 需要任何內存同步 影響。

+0

你是對的。我讀過很多東西,但我似乎已經完全錯過了Lock接口本身...... – 2010-04-05 01:19:10

+0

你能評論一下不穩定的問題嗎? – 2010-04-05 01:19:40

+0

@Steffen Heil:如果我正確地記得它,任何對volatile變量的訪問都只與同一變量的其他訪問同步,即不能保證提供某種通用的內存屏障。 JLS的快速掃描似乎證實了這一點。但是,從這一點開始,我要用大量的鹽,因爲自從我上次與內存模型及其含義的相似之處已經有相當一段時間了...... – Dirk 2010-04-05 01:26:47

4

除了內存模型的語義保證的問題之外,我認爲您發佈的代碼存在一些問題。

  1. 您正在同步鎖定兩次 - 這是不必要的。當使用Lock實現時,您不需要使用​​塊。
  2. 使用Lock的標準慣用法是在try-finally塊中這樣做,以防止意外解鎖(因爲在輸入任何塊時不會自動釋放鎖,與​​塊一樣)。

你應該使用Lock有類似的東西:

lock.lock(); 
try { 
    //do stuff 
} 
finally { 
    lock.unlock(); 
} 
+0

你說得對,我的例子被打破了。我解決了這個問題。 是的,我通常使用try/finally,只是爲了簡短而放棄它。 – 2010-04-05 01:24:43

1

讀取和寫入volatile變量現在強制發生之前和之後的操作順序發生。寫入volatile變量與釋放監視器的效果相同,讀取變量的效果與獲取監視器的效果相同。下面的示例使多一點明確:

volatile boolean memoryBarrier = false; 
int unguardedValue = 0; 

//thread a: 
unguardedValue = 10; 
memoryBarrier = true; 

// thread b 
if (memoryBarrier) { 
    // unguardedValue is guaranteed to be read as 10; 
} 

但是,這一切都這樣說就像是真正使用ReentrantLock,因爲它是設計用來您提供的樣本代碼沒有看。

  1. 周圍用了Java的內置syncronized關鍵字有效地使訪問鎖使用Lock已經單線程的,因此不會給Lock機會做任何實際工作。
  2. 獲取釋放Lock應按照下面的方式來完成,這是的Lock

lock.readLock().lock(); 
try { 
    // Do work 
} finally { 
    lock.readLock.unlock(); 
} 

+0

請參閱有問題的評論。 – 2010-04-05 01:26:27

+0

我知道線程b將不會看到unguardedValue,因爲排序不會影響可見性,而且unguardedValue不是易失性的,因此線程b不會看到它。這是正確的嗎 ? – 2014-04-07 19:48:12

1

Yanamon的Java文檔概述,我不知道你是正確的 - 但出於不同的原因,而不是你正在做的論點。

unguardedVariable變量可以在線程「a」中重新排序,使得它的值在之後設置爲10 memoryBarrier設置爲true。

「有,在一個線程操作將在由程序給出的,只要順序進行不保證重新排序,未檢出線程中 - 即使重新排序是顯而易見的其他線程

Java併發實踐中,Brian Goetz,P34

更新:我說的是在舊的內存模型的情況下實現。所以,如果你想要寫一次運行的任何地方,那麼我的論點就成立了。然而,在新的存儲器模型中,情況並非如此,因爲在存在易失性訪問的情況下,重新排序非易失性變量的語義變得更加嚴格(請參閱http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile)。