01.併發多線程-volatile

可見性

  • 多個線程併發讀寫一個共享變量的時候,有可能某個線程修改了變量的值,可是其餘的線程看不到,也就是對其餘線程不可見

工做原理

image

  • 主要做用是保證可見性以及有序性
  • 不能保證原子性

volatile 怎麼保證可見性和有序性

內存可見性
  1. CPU的內存訪問很慢,因此CPU有幾層的高速緩存,加速內存訪問速度
  2. Java的內存模型對上述又進行了一些列的抽象,JMM(java內存模型)規定全部的變量都在存在主內存的,每一個線程又包含本身的工做內存
  3. 變量在工做內存中修改之後,就會強制將工做內存中的刷回主內存,主內存的變量值立馬變成最新的值
  4. 其他線程中工做內存中的變量緩存直接強制失效過時,不容許直接讀取和使用,當線程再次準備使用的時候,會在主內存中直接讀取最新的值
有序性
  1. JMM是容許編譯器和處理器對指令重排序的,可是規定了as-if-serial語義,即無論怎麼重排序,程序的執行結果不能改變
  2. 針對多線程出現的問題,加上volatile 會禁止重排序,能夠確保程序的有序性
原子性
  1. volatile雖然不能保證原子性,可是在某一些條件下,仍是能提供原子性的
  2. 如讀 64 位數據類型,像 long 和 double 都不是原子的,但 volatile 類型的 double 和 long 就是原子的。

底層時間的機制

  • 若是把加入volatile關鍵字的代碼和未加入volatile關鍵字的代碼都生成彙編代碼,會發現加入volatile關鍵字的代碼會多出一個lock前綴的指令
  • lock前綴指令實際至關於一個內存屏障
  • 內存屏障提供了一下的功能
    1. 重排序時,不能把後面的指令重排序到內存屏障以前的位置
    2. 使得本CPU的Cacahe寫入內存
    3. 寫入動做也會引發別的CPU或者別的內核無效化其Cache,至關於讓新寫入的值對別的線程可見。

應用舉例

1. 狀態量標記
int a = 0;
volatile bool flag = false;

public void write() {
    a = 2;              //1
    flag = true;        //2
}

public void multiply() {
    if (flag) {         //3
        int ret = a * a;//4
    }
}
複製代碼
2.單例模式的實現,典型的雙重檢查鎖定(DCL)
class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
複製代碼

這是一種懶漢的單例模式,使用時才建立對象,並且爲了不初始化操做的指令重排序,給instance加上了volatile。java

相關的面試題目
一、Java 中能建立 volatile 數組嗎?
  • 能,不過建立的是一個指定數組的引用,而不是整個數據,若是改變引用指向的數據,將會受到volatile的保護,可是若是多個線程同時改變數據的元素,volatile標識符就不能祈禱以前的保護做用了
二、volatile 能使得一個非原子操做變成原子操做嗎?
  • 一個典型的例子是在類中的有一個long類型的成員變量,若是你知道該成員變量會被多個線程訪問,那麼最好將這個成員變量設置爲volatile,由於java中讀取long類型不是院子的,須要分紅兩步,若是一個線程正在修改該long變量的值,另外一個此案城可能只能看到該值的通常(前32爲)。可是對一個 volatile 型的 long 或 double 變量的讀寫是原子。
相關文章
相關標籤/搜索