Volatile概述


Volatile概念

volatile是一個特徵修飾符(type specifier)。volatile的做用是做爲指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。——百度百科java

因此呢它主要是兩個做用:一個是線程可見(保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。),一個是防止指令重排序。要理解這些首先呢須要瞭解咱們java的一個內存模型(Java Memory Model,JMM)git


Java Memory Model

咱們知道在java中,實例域、靜態域和數組元素都存儲在堆內存中,堆內存是線程共享,而其餘的一些虛擬機棧等它們的的一些內容是線程獨佔不會有內存可見的問題也不受內存模型影響。Java線程之間的通訊由Java內存模型控制,JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。Java內存模型的抽象示意圖以下:程序員

線程從主內存拿取到某以變量到本身本地內存進行操做,完畢以後再將新的值覆蓋到主內存。以後再有其餘線程拿到此變量獲得一個新的值。經過這樣的方式達到了一個不一樣線程之間的通信,並且這個通訊過程必需要通過主內存。JMM經過控制主內存與每一個線程的本地內存之間的交互,來爲java程序員提供內存可見性保證。數組


線程可見

當一個線程修改了共享變量的值,其餘線程可以當即得知這個修改,這樣的方式來保證單次讀寫操做的同步性。緩存

例子1:j的值會是多少呢?微信

// 線程A執行的代碼
k = 5;

//線程B執行的代碼
int j = k;

答案是沒法肯定。由於即便線程A已經把k的值更新爲5,可是這個操做是在線程A的本地內存中完成的,本地內存所更新的變量並不會當即同步回主內存,所以線程B從主內存中獲得的變量k的值是不肯定的。這就是可見性問題,線程A對變量k修改了以後,線程B沒有當即看到線程A修改的值。多線程

例子2: 新線程會打印出end麼?ide

public class Test {
    private static /*volatile*/ boolean flag = true
    public static void main (String[] args) throws I interrupted Exception {
        new Thread(()-> {
            while (flag) {
                //do sth
            }
        System•out•println("end");
        },name: "server") .start();

        Thread.sleep( millis: 1GGG);
        flag = false
    }
}

答案是不會,新線程的本地內存拿到的flag是true,它一直使用的就是true。即便主線程已經將flag更改並同步到了主內存。新線程的本地空間已經有了flag也不會再去主內存取了。這時使用volatitle關鍵字修飾該變量就能夠保證變量更改進行馬上同步,而且其餘地方使用該變量每次都要從新從主內存拿取。性能

經過兩個例子大概能夠知道的是volatile修飾的變量,變更會及時更新而且線程都會去主內存取而不是到本地優化


指令重排序

實際上就是在執行程序時爲了提升性能,編譯器和處理器經常會對指令作重排序。

  1. 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。

  2. 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。

  3. 內存系統的重排序。因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。

這裏先用一個列子說明重排序的存在,看下面僞代碼:

a=0,b=0,x=0,y=0    
//線程一
a=1;
x=b;
//線程二
b=1;
y=a;

假如不會發生重排序。那麼執行過程當中兩個線程四條指令至少a=1必定在x=b以前,b=1必定在y=a以前。執行順序可能出現六種狀況

//狀況一:(串)線程一執行完後線程二纔開始
a=1;
x=b;
b=1;
y=a;
//結果x=0,y=1

//狀況二:(串)線程一開始時線程二已執行完
b=1;
y=a;
a=1;
x=b;
//結果x=1,y=0
//狀況三:(並)兩線程交叉執行
a=1;
b=1;
x=b;
y=a;
//結果x=1,y=1

//狀況四:(並)兩線程交叉執行
b=1;
a=1;
y=a;
x=b;
//結果x=1,y=1

//狀況五:(並)線程一執行中途線程二開始並執行完
a=1;
b=1;
y=a;
x=b;
//結果x=1,y=1

//狀況五:(並)線程二執行中途線程一開始並執行完
b=1;
a=1;
x=b;
y=a;
//結果x=1,y=1

在不會被調整順序的狀況中結果無非三種(1,0)、(0,1)、(1,1)但實際結果會出現x=0,y=0。也就是說線程的指令是亂序的會進行調整。對於單線程來講調整是不會影響結果的只是提高了效率好比省略一加一減相互抵消的指令或者調整順序,最後結果不影響。

/*
下面這三組就不會發生指令重排
由於改了順序就會影響結果
*/
a=1;
b=a;

a=1;
a=2;

a=b;
b=1;

在上面雙線程的例子中不管是線程一的a=1,x=b仍是線程二的b=1,y=a。在它們本線程中兩條語句並非依賴的,因此調換不影響結果因此會出現調換。但兩個線程放一塊兒變量是依賴的,最後由於重排致使結果不一致。因此在多線程中每每會出現問題因此須要禁止重排,使用volatile那麼指令之間加入內存屏障指令就能夠禁止重排。



本文分享自微信公衆號 - IT那個小筆記(qq1839646816)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索