天天看點

Java記憶體模型FAQ(十)volatile是幹什麼用的原文What does volatile do?

譯者:alex

volatile字段是用于線程間通訊的特殊字段。每次讀volatile字段都會看到其它線程寫入該字段的最新值;實際上,程式員之是以要定義volatile字段是因為在某些情況下由于緩存和重排序所看到的陳舊的變量值是不可接受的。編譯器和運作時禁止在寄存器裡面配置設定它們。它們還必須保證,在它們寫好之後,它們被從緩沖區重新整理到主存中,是以,它們立即能夠對其他線程可見。相同地,在讀取一個volatile字段之前,緩沖區必須失效,因為值是存在于主存中而不是本地處理器緩沖區。在重排序通路volatile變量的時候還有其他的限制。

在舊的記憶體模型下,通路volatile變量不能被重排序,但是,它們可能和通路非volatile變量一起被重排序。這破壞了volatile字段從一個線程到另外一個線程作為一個信号條件的手段。

在新的記憶體模型下,volatile變量仍然不能彼此重排序。和舊模型不同的時候,volatile周圍的普通字段的也不再能夠随便的重排序了。寫入一個volatile字段和釋放螢幕有相同的記憶體影響,而且讀取volatile字段和擷取螢幕也有相同的記憶體影響。事實上,因為新的記憶體模型在重排序volatile字段通路上面和其他字段(volatile或者非volatile)通路上面有了更嚴格的限制。當線程a寫入一個volatile字段f的時候,如果線程b讀取f的話 ,那麼對線程a可見的任何東西都變得對線程b可見了。

如下例子展示了volatile字段應該如何使用:

假設一個線程叫做“writer”,另外一個線程叫做“reader”。對變量v的寫操作會等到變量x寫入到記憶體之後,然後讀線程就可以看見v的值。是以,如果reader線程看到了v的值為true,那麼,它也保證能夠看到在之前發生的寫入42這個操作。而這在舊的記憶體模型中卻未必是這樣的。如果v不是volatile變量,那麼,編譯器可以在writer線程中重排序寫入操作,那麼reader線程中的讀取x變量的操作可能會看到0。

實際上,volatile的語義已經被加強了,已經快達到同步的級别了。為了可見性的原因,每次讀取和寫入一個volatile字段已經像一個半同步操作了

<b>重點注意</b>:對兩個線程來說,為了正确的設定happens-before關系,通路相同的volatile變量是很重要的。以下的結論是不正确的:當線程a寫volatile字段f的時候,線程a可見的所有東西,線上程b讀取volatile的字段g之後,變得對線程b可見了。釋放操作和擷取操作必須比對(也就是在同一個volatile字段上面完成)。

<a></a>

volatile fields are special fields which are used for communicating state between threads. each read of a volatile will see the last write to that volatile by any thread; in effect, they are designated by the programmer as fields for which it is never acceptable to see a “stale” value as a result of caching or reordering. the compiler and runtime are prohibited from allocating them in registers. they must also ensure that after they are written, they are flushed out of the cache to main memory, so they can immediately become visible to other threads. similarly, before a volatile field is read, the cache must be invalidated so that the value in main memory, not the local processor cache, is the one seen. there are also additional restrictions on reordering accesses to volatile variables.

under the old memory model, accesses to volatile variables could not be reordered with each other, but they could be reordered with nonvolatile variable accesses. this undermined the usefulness of volatile fields as a means of signaling conditions from one thread to another.

under the new memory model, it is still true that volatile variables cannot be reordered with each other. the difference is that it is now no longer so easy to reorder normal field accesses around them. writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. in effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread a when it writes to volatile field f becomes visible to thread b when it reads f.

here is a simple example of how volatile fields can be used:

assume that one thread is calling writer, and another is calling reader. the write to v in writer releases the write to x to memory, and the read of v acquires that value from memory. thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it. this would not have been true under the old memory model.  if v were not volatile, then the compiler could reorder the writes in writer, and reader‘s read of x might see 0.

effectively, the semantics of volatile have been strengthened substantially, almost to the level of synchronization. each read or write of a volatile field acts like “half” a synchronization, for purposes of visibility.

<b>important note:</b> note that it is important for both threads to access the same volatile variable in order to properly set up the happens-before relationship. it is not the case that everything visible to thread a when it writes volatile field f becomes visible to thread b after it reads volatile field g. the release and acquire have to “match” (i.e., be performed on the same volatile field) to have the right semantics.