目錄
一、簡介
synchronized是阻塞式同步,在線程競爭激烈的情況下會升級爲重量級鎖。而volatile就可以說是java虛擬機提供的最輕量級的同步機制。但它同時不容易被正確理解,也至於在併發編程中很多程序員遇到線程安全的問題就會使用synchronized。
被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現數據髒讀的現象。當然這是在多線程的情況下,單線程沒啥意義。
volatile也能解決數據不一致問題,主要表現在內存層級上,大家繼續往下看。
二、JMM與CPU
我們在第一節JMM中介紹過CPU的緩存模型,我們先回顧下
如下圖所示,因爲每個cpu有自己獨立的寄存器和緩存,但是內存是共享的,所以會存在內存不一致性問題。
大部分一致性解決方案就是:
1、總線枷鎖 (粒度太大)
2、MESI 緩存一致性協議,基於cache line 實現
a. 讀操作:不做任何事情,把Cache中的數據讀到寄存器
b. 寫操作:發出信號通知其他的CPU將該變量的Cache line置爲無效,其他的CPU要訪問這個變量的時候,只能從內存中獲取。
JAVA內存模型描述:
1) 主存中的數據所有線程都可以訪問(共享數據)
2) 每個線程都有自己的工作空間,(本地內存)(私有數據)
3) 工作空間數據:局部變量、內存的副本
4) 線程不能直接修改內存中的數據,只能讀到工作空間來修改,修改完成後刷新到內存
這個模型結構大家現在是不是已經深入刻在腦子裏面了?
使用了Volatile修飾的變量,被一個線程修改了以後,另外一個線程在讀取到這個變量的時候就是最新的值,所以纔會成爲可見性,Volatile可以理解成爲就是在JMM上實現了類似MESI協議。MESI在硬件層級上是使cache line失效。Volatile就是在JMM上實現變量修改後,其他線程馬上感知到。怎麼感知的呢?cpu的總線會將信息傳遞給各個線程。
三、Volatile的語句分析
Volatile的作用: 就是讓其他的線程能夠馬上感知到某一線程對某個變量的修改。
volatile主要保證2個特性,可見性(多個現場見自己的工作空間裏面的數據保持一致)和有序性。不保證原子性。
1、保證可見性:
對共享變量的修改,其他的線程馬上能感知到;
不能保證原子性,如果N個線程都從內存取出值到工作內存,修改後,又一起寫回去。就會出現多次覆蓋。因爲失效是工作空間裏面的失效了。但是如果都已經處於寫回狀態時,就沒辦法了,會多次一樣的值覆蓋。
2、保證有序性:
編輯器優化和指令重排。
重排序(編譯階段、指令優化階段);
輸入程序的代碼順序並不是實際執行的順序;
重排序後對單線程沒有影響,對多線程有影響;
對於Volatile修飾的變量 在重排序的時候有如下規則:
1)volatile之前的代碼不能調整到它後面
2)volatile之後的代碼不能調整到它之前
3)volatile修飾的代碼位置不變
例如:
Int i=0;
Int a=3;
Int b=5;
Volatile Int j=3;
順序重排後不能出現這樣的情況。
Volatile Int j=3;
Int i=0;
Int a=3;
Int b=5;
volatile的實現就是加了一個鎖:LOCK。
通過javap 命令,將字節碼文件反編譯。觀察反編譯的結果,對於volatile修飾的變量,發現反編譯得到的字節碼並沒有什麼幫助,和不加volatile修飾的變量沒有任何區別。也就是說,字節碼層面volatile變量並沒有什麼不同。
下面通過查看Java的彙編指令,查看Java代碼最真實的運行細節。
大家可以在網上找個hsdis-amd64.dll 然後放到jdk文件夾下面/jre/bin/server中,然後再執行的時候加上:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
執行就可以看到字節碼了。這個lock cpmxchg 就好比是一個柵欄一樣,使得上面的指令無法到下面去,下面的指令無法到上面去,達到了有序性的目的。
四、Volatile使用場景
1、狀態標誌(開關模式)
代碼如下:
public class ShutDowsnDemmo extends Thread{
private volatile boolean started=false;
@Override
public void run() {
while(started){
dowork();
}
}
public void shutdown(){
started=false;
}
}
2、雙重檢查鎖(double-checked-locking)
常見到的是單例模式,下節我們在主要介紹單例模式。
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
instance=new Singleton();
}
}
return instance;
}
}
3、需要利用順序性(防止被指令重排)
public class VolaitleDemo1 {
private static volatile int m=0;
public static void main(String[] args) {
int i=0,j=0;
i++;
m++;
j=i+m;
System.out.println("last value : "+j);
}
}
五、volatile與synchronized的區別
1、使用上的區別
Volatile只能修飾變量;
synchronized只能修飾方法和語句塊;
2、對原子性的保證
synchronized可以保證原子性;
Volatile不能保證原子性;所以不要用來做值增加等操作,當開關還是可以的。
3、對可見性的保證
都可以保證可見性,但實現原理不同
Volatile對變量加了lock;
synchronized使用monitor 的 monitorEnter和monitorexit 或者 信號量
4、對有序性的保證
Volatile能保證有序;
synchronized可以保證有序性,但是代價太大(重量級鎖)併發退化到串行,所以不要在代碼塊裏面寫太多的業務代碼。
5、阻塞
synchronized引起阻塞
Volatile不會引起阻塞