併發編程----6、Volatile關鍵字

目錄

 

一、簡介

二、JMM與CPU

三、Volatile的語句分析

四、Volatile使用場景

五、volatile與synchronized的區別


一、簡介

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不會引起阻塞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章