2008-12-12 60 views
28

多年來,我試着瞭解處理內存模型和併發性的Java規範的part。我不得不承認我失敗了。是的'我瞭解鎖和「同步」,並等待()和通知()。我可以很好地使用它們,謝謝。我甚至對「易變」的含義有一個模糊的概念。但所有這些都不是來自語言規範 - 而是來自一般經驗。Java內存模型 - 有人可以解釋它嗎?

這是我問的兩個示例問題。我對特定的答案不太感興趣,因爲我需要了解答案是如何從規範中派生的(或者可能是我如何得出規範沒有答案的結論)。

  • 什麼是「volatile」呢?
  • 寫入可變原子?它依賴於變量的類型嗎?

回答

33

我不會嘗試在這裏實際回答你的問題 - 相反,我會將你重定向到我看到的關於此主題的建議的書籍:Java Concurrency in Practice

一個字的警告:如果有在這裏的答案,預計其中不少人會是錯的。我不打算髮布詳細信息的原因之一是因爲我非常確定我會至少在某些方面出錯。我的意思是,當我說所有認爲他們能夠回答這個問題的人確實有足夠的嚴格把握時機的機率幾乎爲零時,他們對社區沒有任何不尊重。 (喬·達菲最近發現有些是很驚訝的.NET存儲模式。如果他能得到它錯了,所以凡人能像我們一樣。)


我將提供只是一個方面的一些見解,因爲它經常被誤解:

波動性和原子性之間存在差異。人們經常認爲原子寫入是不穩定的(即,如果寫入是原子的,則不需要擔心內存模型)。這不是真的。

波動性是關於一個線程執行讀操作(邏輯上,在源代碼中)是否會「看到」另一個線程所做的更改。

原子性是關於是否有任何機會,如果看到變化,只會看到部分變化。

例如,寫入一個整數字段。這是保證是原子的,但不易變。這意味着,如果我們有(開始於foo.x = 0):

Thread 1: foo.x = 257; 
Thread 2: int y = foo.x; 

有可能爲y爲0或者257。這不會是任何其它值(例如256或1)由於原子性約束。但是,即使你知道在線程2中的代碼在線程2中的代碼執行後,可能會出現奇怪的緩存,內存訪問「正在移動」等。使變量x易失性將解決此問題。

我會把其餘的部分留給真正的誠實專家。

7

我不會試着在這裏解釋這些問題,而是請您參考Brian Goetz關於這個主題的優秀書。

這本書是「實踐中的Java併發性」,可以在Amazon或任何其他排序良好的計算機文獻商店中找到。

13
  • volatile變量可以被緩存線程局部,所以不同的線程可能會看到在同一時間不同的值;寫入32位或更小的變量時保證爲原子的(implied here);沒有那麼對於longdouble,雖然64位的JVM可能實現它們的原子操作
4

這是一個很好的鏈接,可以給你一點點深入的信息:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

+0

感謝您的鏈接!它不會取代一本書(我會得到),但它確實給了我一個洞見:我習慣於考慮同步,而內存模型更多地關注重新排序。我需要學會區分這兩者,並想到最後一個。 – Arkadiy 2008-12-12 18:40:04

0

一個概念可能是有用的:數據(數據)和副本。

如果你聲明一個變量,讓我們說一個字節,它駐留在內存中的某個地方,在一個數據段(粗略地說)。內存中有8位用於存儲該信息。

但是,可能會有多個數據副本,在您的機器中移動。由於各種技術原因,例如線程的本地存儲,編譯器優化。如果我們有多個副本,它們可能不同步。

所以你應該始終記住這個概念。它不僅適用於java類字段,而且適用於cpp變量,數據庫記錄(記錄狀態數據被複制到多個會話等中)。變量,其隱藏/可見副本以及細微的同步問題將永遠存在。

3

我最近發現an excellent article解釋揮發性爲:

首先,你要了解一點關於Java存儲模型。多年來我一直在努力解釋這個問題。到今天爲止,我能想到的最好的方式來形容,那就是如果你想像這樣說:

  • 在Java中的每個線程發生在一個單獨的內存空間(這顯然是不真實的,所以大家多多包涵在這一個)。

  • 您需要使用特殊的機制來保證在這些線程之間進行通信,就像在消息傳遞系統中一樣。

  • 內存寫入發生在一個線程中可以「泄漏」並被另一個線程看到,但這並不是保證。如果沒有明確的溝通,你不能保證哪些寫入被其他線程看到,甚至不能保證他們看到的順序。

Java volatile修飾符是保證線程間通信發生的特殊機制的一個例子。當一個線程寫入一個volatile變量,另一個線程看到寫入時,第一個線程會告訴第二個線程關於所有內存內容,直到它執行寫入該volatile變量。

其他鏈接: http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html http://www.javaperformancetuning.com/news/qotm030.shtml

+1

這不是N,它是N + 1!看到我自己的答案:) – Arkadiy 2012-08-17 12:30:07

1

其他答案以上是完全正確的,你的問題是不適合心臟佯攻。然而,我理解你真正想要得到的東西的痛苦 - 因爲我希望你回到java的世界編譯器和低層前輩 - 即彙編,C和C++。

閱讀有關不同種類的障礙('柵欄')。瞭解什麼是內存屏障,以及它在何處是必要的,將幫助您直觀地掌握volatile的作用。

0

另一個試圖提供我從這裏和其他來源的答案中得到的東西的總結(第一次嘗試離基地很遠,我希望這個更好)。

Java內存模型是關於將一​​個線程中寫入內存的值傳播給其他線程,以便其他線程在從內存中讀取時可以看到它們。

總之,如果你獲得一個互斥鎖,任何釋放該互斥鎖的線程所寫的任何東西都將對你的線程可見。

如果您讀取一個易失性變量,在讀取它之前寫入該易失性變量的任何內容對於讀取線程都是可見的。此外,寫入變量之前寫入變量的線程完成的任何易失性變量寫入都是可見的。而且,在Java 1.5中,任何寫操作都是可變的或不可變的,在寫入volatile變量之前寫入到volatile變量的任何線程上發生的任何寫操作都將對您可見。

構建對象後,可以將其傳遞給另一個線程,並且所有最終成員都將在新線程中可見並完全構建。對非最終成員沒有類似的保證。這使我認爲賦予最終成員充當了寫入volatile變量(內存圍欄)。

線程在Runnable退出之前寫入的任何內容對執行join()的線程都是可見的。線程在執行start()之前寫入的任何內容都可以在生成的線程中看到。

另一件要提到的事情是:volatile變量和同步有一個很少提及的函數:除了刷新線程緩存並提供一次一個線程訪問外,它們還可以防止編譯器和CPU重新排列同步讀寫邊界。

沒有一個是新的,其他答案都表明它更好。我只是想寫這個來清理我的頭。