volatile關(guān)鍵字可以說是Java虛擬機(jī)提供的最輕量級的同步機(jī)制渠啤,但是它并不容易彎曲被正確壤追、完整的理解撩轰。
volatile的特性
volatile關(guān)鍵字是Java虛擬機(jī)提供的最輕量級的同步機(jī)制坟乾。Java內(nèi)存模型對volatile專門定義了一些特殊的訪問規(guī)則档桃。
當(dāng)一個變量被volatile修飾后枪孩,它將具備兩種特性:
1. 可見性
** 第一是保證此變量對所以線程的可見性** ,這里的"可見性"是指當(dāng)一個線程修改了這個變量的值蔑舞,新值對于其它線程來說是可以立即得知的斗幼。而普通變量不能做到這一點(diǎn),普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成,例如,線程A修改一個普通的變量的值畔咧,然后向主內(nèi)存進(jìn)行回寫,另外一條線程B在線程A回寫完成了之后再從主內(nèi)存進(jìn)行讀取操作拜隧,新變量值才會對線程B可見雀费。
關(guān)于volatile變量的可見性宋光,經(jīng)常會被開發(fā)人員誤解逛漫,認(rèn)為以下描述成立:"volatile變量對所有線程是立即可見,對volatile變量所有的寫操作都能立刻反映到其他線程之中菩暗,換句話說掏熬,volatile變量在各個線程中是一致的,所以基于volatile變量的運(yùn)算在并發(fā)情況下是安全的"。這句話論據(jù)部分并沒有錯履恩,但是其論據(jù)并不能得出"基于volatile變量的運(yùn)算在并發(fā)情況下是安全的"這個結(jié)論。volatile變量在各個線程的工作內(nèi)存中不存在一致性問題(在各個線程的工作內(nèi)存中暇屋,volatile變量也可以存在不一致的情況似袁,但由于每次使用之前都要先刷新,執(zhí)行引擎看不到不一致的情況咐刨,因此可以認(rèn)為不存在一致性問題)昙衅,但是Java里面的運(yùn)算并非是原子操作,導(dǎo)致volatile變量的運(yùn)算在并發(fā)情況下一樣是不安全的定鸟。
例如:
package com.bytebeats.codelab;
import java.util.concurrent.CountDownLatch;
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
*/
public class VolatileDemo {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
demo.run();
}
public void run() throws InterruptedException {
int threadNum = 20;
final CountDownLatch latch = new CountDownLatch(threadNum);
for (int i=0; i<threadNum; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int x=0; x<50; x++){
incr();
}
latch.countDown();
}
});
t.start();
}
//等待所有線程執(zhí)行完累加操作
latch.await();
System.out.println(race);
}
private volatile int race = 0;
private void incr(){
race++;
}
}
模擬了20個線程對race變量進(jìn)行自增操作而涉,如果這段代碼是并發(fā)安全的話,最后輸出的結(jié)果應(yīng)該為1000联予。但事實上每次運(yùn)行上述代碼啼县,輸出的結(jié)果可能都會不一樣材原,都是一個小于1000的數(shù)。
究其原因季眷,就是Java 中 race++
不是原子操作余蟹。
由于volatile變量,只能保證可見性子刮,在不符合以下兩條規(guī)則的情況下威酒,我們?nèi)匀恍枰ㄟ^加鎖(使用synchronized 或 java.util.concurrent中的原子類)來保證原子性。
- 運(yùn)算結(jié)果并不依賴變量的當(dāng)前值挺峡,或者能夠確保只有單一的線程修改變量的值葵孤。
- 變量不需要與其他的狀態(tài)變量共同參與不變約束。
2. 禁止指令重排序優(yōu)化
使用volatile變量的第二個語義是禁止指令重排序優(yōu)化橱赠,
原子性尤仍、可見性、有序性
Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理 原子性狭姨、可見性和有序性 這3個特征來建立的宰啦。我們來逐個看一下哪些操作實現(xiàn)了這3個特性。
原子性
由Java內(nèi)存模型來直接保證的原子性變量操作包括:read送挑、load绑莺、assign、use惕耕、store和write纺裁,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問操作是具備原子性的(例外的是long和double的非原子性協(xié)定)。
如果硬要程序需要一個更大范圍的原子性保證(經(jīng)常會遇到)司澎,Java內(nèi)存模型還提供了synchronized 關(guān)鍵字和Lock欺缘,在synchronized 塊之間的操作也具備原子性。
可見性
可見性是指當(dāng)一個線程修改了這個變量的值挤安,新值對于其它線程來說是可以立即得知這個修改谚殊。
Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值 這種依賴主內(nèi)存作為傳遞媒介的方式來實現(xiàn)可見性的蛤铜,無論是普通變量還是volatile變量都是如此嫩絮,普通變量與volatile變量的區(qū)別是:volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新围肥。因此剿干,可以說volatile保證了多線程操作時 變量的可見性,而普通變量則不能保證這一點(diǎn)穆刻。
除了volatile之外置尔,Java還有兩個關(guān)鍵字能實現(xiàn)可見性,即synchronized和final氢伟。