多線程之Volatile關鍵字

概述

volatile在多線程併發時有兩個作用,一.實現線程間可見性。二.禁止指令重排序

線程間可見性

例子仍然可以在github中下載
爲什麼存在線程間可見性問題?
根據java內存模型(JMM ),每個線程都有一個工作內存,共享一個主內存,程序運行時會把主內存的數據,拷貝到工作內存中。併發執行的情況下,如果沒有加volatile時,cpu不能保證線程間內存的可見性(cpu會在不忙碌時同步主內存的數據)。
加volatile怎麼保證?
加volatile後,虛擬機會保證每個線程寫完以後同步到主內存中,並且線程讀變量時會把本地變量置爲失效,從主內存中讀共享變量,另外volatile操作底層使用的是lock指令。

禁止指令重排序

什麼是指令重排序? 
爲了充分利用cpu,指令的執行順序可能會發生改變,即在後面的指令可能提前執行,這就是指令重排序,但指令重排序必須保證As-If-Serial語義。

As-If-Serial語義
as-if-serial語義的意思是:不管怎麼進行指令重排序,單線程內程序的執行結果不能被改變。編譯器,處理器進行指令重排序都必須要遵守as-if-serial語義規則。
爲了遵守as-if-serial語義,編譯器和處理器對存在依賴關係的操作,都不會對其進行重排序,因爲這樣的重排序很可能會改變執行的結果,但是對不存在依賴關係的操作,就有可能進行重排序。

單線程有序,多線程亂序 因爲指令重排序的存在,及程序必須保證as-if-serial語義,在本線程下觀察,程序是正確按順序執行的。但如果多線程共享操作,別的線程就可能受你重排序的影響。
舉個例子

public static Singleton getInstance() {
    if(singleton == null) {
        synchronized (Singleton.class) {
            if(singleton == null) {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

上面這段代碼大家都很熟悉,double check lock 創建單例。發生指令重排會有什麼問題?
我們先看singleton = new Singleton()有哪些指令

  1. new 指令分配空間(這時對象的屬性都是默認值)
  2. init 初始化對象數據
  3. astore 將singleton變量指向new出來的Singleton對象

好的假如現在有兩個線程A、B併發執行到getInstance方法,線程A判斷singleton是空,然後加鎖,加鎖成功,判斷singleton爲空,執行singleton = new Singleton(), 這裏如果發生了指令重排,先執行new指令分配對象空間,再執行astore將指針指向new出來的數據,注意這時候還沒有執行init方法,沒有初始化對象,這時線程B判斷singleton是否爲空,不爲空,直接返回使用。這裏就有問題了,線程B使用了一個半初始化狀態的Singleton對象。 所以DCL創建單例要加volatile.

volatile內存屏障
Java虛擬機規範中規定:

  • 在每個volatile寫操作的前面插入一個StoreStore屏障。在每個volatile寫操作的後面插入一個StoreLoad屏障。

  • 在每個volatile讀操作的後面插入一個LoadLoad屏障。在每個volatile讀操作的後面插入一個LoadStore屏障。

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