併發是程序界的量子物理,然而volatile又是量子物理中薛定諤的貓。本篇文章試圖系統的梳理一下java中的Volatile關鍵字。這篇譯文可能幫助你更好的理解volatile關鍵字。
使用volatile關鍵字是解決同步問題的一種有效手段。 java volatile關鍵字預示着這個變量始終是「存儲進入了主存」。更精確的表述就是每一次讀一個volatile變量,都會從主存讀取,而不是CPU的緩存。一樣的道理,每次寫一個volatile變量,都是寫回主存,而不只僅是CPU的緩存。
javascript
Java 保證volatile關鍵字保證變量的改變對各個線程是可見的。這看起來有點抽象,不過將緊接着說明這一點。
咱們知道,每個線程都有本身的線程棧。多線程在操做非volatile變量的時候,都會從主存拷貝變量值到本身的棧內存中間,而後再操做變量。在多個線程的狀況下,若是一個線程修改了變量值還未回寫到主內存,另外一個線程讀取的就是一箇舊的值,這樣會出現問題,由於讀到的變量不是最新的。實際上,在多核CPU中間,因爲每一個CPU都有本身的緩存,一樣會存在主存與CPU緩存之間數據不一致的狀況。所以,在C語言中,也有volatile關鍵字。(譯者注:實際上,若是在CPU的層面知足volatile特性,那麼線程棧就必定知足。由於從volatile語義來說,jvm線程每次只從主存讀寫volatile變量,而主存的volatile變量又在CPU層面知足volatile語義)
想象一種這樣的狀況,有兩個或者更多的線程訪問一個共享對象,這個共享對象包括了一個counter變量:
public class SharedObject {java
public int counter = 0;複製代碼
}
再想象一下,只有線程1對counter變量加一,可是線程一和線程2倒是同時讀到這個變量。緩存
若是這個contouer變量沒有被聲明爲volatile。
就不能保證counter變量從cpu緩存回寫到主存。這就意味着counter變量在cpu緩存中的值與主存中值不一致。多線程
這就是所謂的線程不能看到變量最新值的問題。由於另一個線程並無及時將變量寫回到主存。這樣一個線程的人更新對其餘線程是不可見的。併發
經過聲明counter變量是一個volatile變量,這樣全部counter變量的更改就會被當即寫入主存。一樣,對counter變量的讀也從主存裏面讀。下面是如何聲明一個volatile變量:app
public class SharedObject {
public volatile int counter = 0;
}複製代碼
經過聲明volatile變量就保證了對其餘線程寫的可見性。jvm
java5中的volatile關鍵字不僅是保證從主存中讀寫數據,實際上,volatile還保證以下的狀況:性能
讀和寫volatile變量的指令沒法被JVM重排序(JVM爲了提升性能能夠重排序一些指令,只要程序的行爲與排序前同樣)可是volatile變量卻沒法重排序,也就是volatile變量的讀和寫沒法被打亂在其餘變量中間。不論是什麼指令,老是在volatile變量讀寫以後發生。優化
下面將會詳細的解釋這一點。ui
當一個線程寫一個volatile變量,而後不只僅是volatile變量自己自身寫入到主存。全部其餘的在寫volatile變量以前也會被刷入主存。當一個線程讀volatile變量的時候,它也會從主存讀取其餘變量。(譯者注:注意是全部的變量。每次在寫入volatile變量的時候,線程棧裏面的全部的共享變量都將刷回主存,而不只僅是在volatile變量聲明以前的變量)
看下面這個例子,sharedObject.counter是一個volatile變量:
Thread A:
sharedObject.nonVolatile = 123;
sharedObject.counter = sharedObject.counter + 1;
Thread B:
int counter = sharedObject.counter;
int nonVolatile = sharedObject.nonVolatile;複製代碼
當線程A寫在寫入volatile變量sharedObject.counter以前寫入一個非volatile變量,而後再寫入volatile變量,這個時候非volatile變量sharedObject.nonVolatile 也會被寫入主存。
當線程B開始讀一個volatile變量sharedObject.counter,而後全部的sharedObject.nonVolatile以及
sharedObject.counter都會從主存讀取。這個時候sharedObject.nonVolatile值與線程A中的值是同樣的。
開發者可使用這種擴展的可視性來優化線程之間的可視性:不是對每一個變量都聲明爲volatile變量,而是隻須要聲明其中一部分變量爲volatile。下面是Exchanger類,就利用了上述的原則:
public class Exchanger {
private Object object = null;
private volatile hasNewObject = false;
public void put(Object newObject) {
while(hasNewObject) {
//wait - do not overwrite existing new object
}
object = newObject;
hasNewObject = true; //volatile write
}
public Object take(){
while(!hasNewObject){ //volatile read
//wait - don't take old object (or null)
}
Object obj = object;
hasNewObject = false; //volatile write
return obj;
}
}複製代碼
線程A一遍又一遍的調用put()方法。線程B一遍又一遍的調用take方法。這個Exchanger可以在合理使用volatile關鍵字的狀況下工做的很好。只要線程A只調用put方法,線程b只調用take方法。
然而,JVM是能夠對指令進行優化的。若是JVM對指令優化,打亂了順序,會出現什麼樣的效果呢?下面這段代碼多是執行的順序之一:
while(hasNewObject) {
//wait - do not overwrite existing new object
}
hasNewObject = true; //volatile write
object = newObject;複製代碼
注意到volatile變量hasNewObject如今在object被設置以前執行了。這個對於JVM來講看起來好像是合法的,由於這兩個值的寫入指令相互是沒有依賴的,JVM能夠對它們重排序。
然而,重排指令有可能影響到object變量的可見性。首先,線程B看見在線程A尚未對object賦值以前就看見了hasNewObject是一個true變量,這樣操做線程B讀取了一個空值。其次,這甚至不能保證object變量會被及時的寫入到主存。(固然,下一次線程A更改volatile變量的時候就會被刷進主存)
爲了阻止上面的任何一種狀況發生,volatile保證了「happens before 」特性。happens-before特性保證volatile變量的讀寫不能被重排序。也就是對volatile變量的讀寫不能插入到其餘的任何指令中。
看下面這個例子:
sharedObject.nonVolatile1 = 123;
sharedObject.nonVolatile2 = 456;
sharedObject.nonVolatile3 = 789;
sharedObject.volatile = true; //一個 volatile 變量
int someValue1 = sharedObject.nonVolatile4;
int someValue2 = sharedObject.nonVolatile5;
int someValue3 = sharedObject.nonVolatile6;複製代碼
JVM 可能重排序前三個指令。只要他們所有在volatile寫入指令前發生(他們必須在volatile寫入前所有執行)
相似的,JVM可能重排序最後三個指令。只要volatile變量寫操做在它們前發生。最後這三個指令都不能被排在volatile變量寫指令前面。
這就是最基本的javavolatile變量的happens before原則。
即便是volatile關鍵字保證了讀寫都是從主存讀取,然而仍然有寫狀況不能簡單的使用variable變量來解決。在早先講到的例子中,當線程1寫入一個變量counter這個volatile以後,就能保證線程2讀到這個最新的值。
事實上,若是線程在寫volatile變量並不依賴於這個volatile以前的值,那麼在寫的過程當中,主存中仍然是當前的值。
而後一個線程開始讀這個volatile變量。那麼這個線程讀到的值就是舊的值,可見性就是不正確的。這就會形成讀變量和寫變量之間的競爭。volatile關鍵字只是保證了下一次讀取的是最新的變量,可是在另一個變量寫入的過程當中,讀到的值仍然是舊的。(譯者注:若是是多個CPU先寫後讀,在寫的過程當中實際上會發出信號,告知其緩存已經失效,因此並不會存在這種狀況;至於先讀後寫,讀取一箇舊的值的時候要在代碼裏保證並不會引起任何錯誤。)。