Volatile關鍵字與線程安全
volatile關鍵字,它的語義有二:
1.volatile修飾的變量對於其餘線程具備當即可見性
即被volatile修飾的變量值發生變化時,其餘線程能夠立馬感知。而對於普通變量,值發生變化後,須要通過store、write過程將變量從當前線程的工做內存寫入主內存,其餘線程再從主內存經過read、load將變量同步到本身的工做內存,因爲以上流程時間上的影響,可能會致使線程的不安全。
固然要說使用volatile修飾過的變量是線程安全的,也不全對。由於volatile是要分場景來講的:若是多個線程操做volatile修飾的變量,且此時的「操做」是原子性的,那麼是線程安全的,不然不是。如:
volatile int i=0;線程1執行: for(;i++;i<100);線程2執行: for(;i++;i<100);
最後 i 的結果不必定會是200(線程不安全),由於i++操做不是原子性操做,它涉及到了三個子操做:從主內存取出i、i+一、將結果同步回主內存。那麼就有可能一個線程拿到最新值,正開始執行第二個子操做,而值還將來得及改變時,第二個線程就已經拿到一樣的值開始執行第二個子操做了。這樣一來,就有可能兩個線程給同一個值加了一次1,因此就算有volatile修飾也是無力迴天。
這時,咱們應該使用synchronize或concurrent原子類來保證「操做」的原子性。故volatile的使用場景應該是:修飾的變量的有關操做都是原子性的時候。好比修飾一個控制標誌位:
volatile boolean tag=true;線程1 while(tag){};線程2 while(tag){};
當tag=false時,兩個線程都能立刻感知到並中止while循環,由於簡單的賦值語句屬於原子操做(賦予具體的值而不是變量),它只負責把主內存的tag同步爲true。能實現可見性的關鍵字除了volatile,還有synchronize與final,synchronize是由於變量執行解鎖操做前,會把變量同步到主內存(自帶可見性);而final則是被其修飾的變量一旦初始化,且構造器沒有把this引用傳遞到外面去的狀況下,其餘線程就能夠看見它的值(由於它永不發生變化)。
2.禁止指令重排序
new一個對象能夠分解爲以下的3行僞代碼
memory=allocate(); //1:分配對象的內存空間ctorInstance(memory); //2:初始化對象instance=memory; //3:設置instance指向剛分配的內存地址
上面3行代碼中的2和3之間,可能會被重排序(在一些JIT編譯器上,這種重排序是真實發生的)。2和3之間重排序以後的執行時序以下
memory=allocate(); //1:分配對象的內存空間instance=memory; //3:設置instance指向剛分配的內存地址,注意此時對象尚未被初始化ctorInstance(memory); //2:初始化對象
若是發生重排序,另外一個併發執行的線程B就有可能在還沒初始化對象操做前就拿走了instance,但此時這個對象可能尚未被線程真正初始化,所以這是線程不安全的。
Java先行發生原則:
1.程序次序規則:在一個線程內,按照程序代碼順序(準確說應是控制流順序),先寫的先發生,後寫的後發生。
2.管程鎖定規則:一個解鎖操做先於後面對該鎖的鎖定操做。
3.volatile變量規則:對一個volatile變量的寫操做先行發生於後面對這個變量的讀操做。
4.線程啓動規則:線程對象的start()方法先行發生於此線程的每個動做。
5.線程終止規則:線程的全部操做都先於此線程的終止檢測。
6.線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。可經過Thread.interrupted()方法檢測是否會有中斷將發生。
7.對象終結規則:一個對象的初始化發生先行於它的finalize()方法的開始。
8.傳遞性:若是操做A先於B,B先於C,那麼A先於C。
例:
private int value=0;public void setValue(int value){this.value=value;}public int getValue(){return this.value;}
若是線程1調用setValue(1)方法,線程2調用getValue(),那麼獲得的value是0仍是1呢,這是不肯定的。由於它不知足上面的先行發生原則:由於不是在一個線程,因此不符合程序次序規則;由於沒有同步塊,也就不存在加鎖和解鎖,所以也不符合管程鎖定規則;沒有volatile修飾,也就不存在volatile變量規則,更沒有後面的線程相關規則和傳遞性可言。針對此,可作如下修改:
將上面的setter、getter方法都用synchronize修飾,使其知足管程鎖定規則;或者使用volatile修飾,由於setValue()是基本的賦值操做,屬於原子操做,所以符合volatile的使用場景。
注:
1.從上總結,線程安全通常至少須要兩個特性:原子性和可見性。
2.synchronize是具備原子性和可見性的,因此若是使用了synchronize修飾的操做,那麼就自帶了可見性,也就再也不須要volatile來保證可見性了。
2.對於上面代碼,若想實現線程安全的數字的自增自減等操做,也可以使用 java.util.concurrent.atomic包來進行無鎖的原子性操做。在其底層實現中,如AtomicInteger,一樣是使用了volatile來保證可見性;使用Unsafe調用native本地方法CAS,CAS採用總線加鎖或緩存加鎖方式來保證原子性。
參考:
(Java併發編程:volatile關鍵字解析)http://www.importnew.com/18126.html