1. 程式人生 > >Volatile關鍵字與線程安全

Volatile關鍵字與線程安全

它的 方法 current integer ger 取出 div 動作 得到

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、將結果同步回主內存。那麽就有可能一個線程拿到最新值,正開始執行第二個子操作,而值還未來得及改變時,第二個線程就已經拿到同樣的值開始執行第二個子操作了。這樣一來,就有可能兩個線程給同一個值加了一次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

Volatile關鍵字與線程安全