可見性
多個線程併發讀寫一個共享變量的時候,有可能某個線程修改了變量的值,可是其餘的線程看不到,也就是對其餘線程不可見
工做原理
volatile 怎麼保證可見性和有序性
內存可見性
CPU的內存訪問很慢,因此CPU有幾層的高速緩存,加速內存訪問速度
Java的內存模型對上述又進行了一些列的抽象,JMM(java內存模型)規定全部的變量都在存在主內存 的,每一個線程又包含本身的工做內存
變量在工做內存中修改之後,就會強制將工做內存中的刷回主內存,主內存的變量值立馬變成最新的值
其他線程中工做內存中的變量緩存直接強制失效過時,不容許直接讀取和使用,當線程再次準備使用的時候,會在主內存中直接讀取最新的值
有序性
JMM是容許編譯器和處理器對指令重排序的,可是規定了as-if-serial語義,即無論怎麼重排序,程序的執行結果不能改變
針對多線程出現的問題,加上volatile 會禁止重排序,能夠確保程序的有序性
原子性
volatile雖然不能保證原子性,可是在某一些條件下,仍是能提供原子性的
如讀 64 位數據類型,像 long 和 double 都不是原子的,但 volatile 類型的 double 和 long 就是原子的。
底層時間的機制
若是把加入volatile關鍵字的代碼和未加入volatile關鍵字的代碼都生成彙編代碼,會發現加入volatile關鍵字的代碼會多出一個lock前綴的指令
lock前綴指令實際至關於一個內存屏障
內存屏障提供了一下的功能
重排序時,不能把後面的指令重排序到內存屏障以前的位置
使得本CPU的Cacahe寫入內存
寫入動做也會引發別的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 變量的讀寫是原子。