今天咱們聊聊volatile底層原理;java
Java語言規範對於volatile定義以下:編程
Java編程語言容許線程訪問共享變量,爲了確保共享變量可以被準確和一致性地更新,線程應該確保經過排它鎖單獨得到這個變量。緩存
首先咱們從定義開始入手,官方定義比較拗口。通俗來講就是一個字段被volatile修飾,Java的內存模型確保全部的線程看到的這個變量值是一致的,可是它並不能保證多線程的原子操做。這就是所謂的線程可見性。咱們要知道他是不能保證原子性的。多線程
Java線程之間的通訊由Java內存模型(JMM)控制,JMM決定一個線程對共享變量的修改什麼時候對另一個線程可見。JMM定義了線程與主內存的抽象關係:線程之間的變量存儲在主內存(Main Memory)中,每一個線程都有一個私有的本地內存(Local Memory)保存着共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。編程語言
若是線程A與線程B通訊:spa
線程A要先把本地內存A中更新過的共享變量刷寫到主內存中。線程
線程B到主內存中讀取線程A更新後的共享變量code
計算機在運行程序時,每條指令都是在CPU中執行的,在執行過程當中勢必會涉及到數據的讀寫。咱們知道程序運行的數據是存儲在主存中,這時就會有一個問題,讀寫主存中的數據沒有CPU中執行指令的速度快,若是任何的交互都須要與主存打交道則會大大影響效率,因此就有了CPU高速緩存。CPU高速緩存爲某個CPU獨有,只與在該CPU運行的線程有關。orm
有了CPU高速緩存雖然解決了效率問題,可是它會帶來一個新的問題:數據一致性。在程序運行中,會將運行所須要的數據複製一份到CPU高速緩存中,在進行運算時CPU再也不也主存打交道,而是直接從高速緩存中讀寫數據,只有當運行結束後纔會將數據刷新到主存中。cdn
舉個例子:
i++;
當線程運行這行代碼時,首先會從主內存中讀取i,而後複製一份到CPU高速緩存中,接着CPU執行+1的操做,再將+1後的數據寫在緩存中,最後一步纔是刷新到主內存中。在單線程時沒有問題,多線程就有問題了。
以下:假若有兩個線程A、B都執行這個操做(i++),按照咱們正常的邏輯思惟主存中的i值應該=3,但事實是這樣麼?
分析以下:
兩個線程從主存中讀取i的值(1)到各自的高速緩存中,而後線程A執行+1操做並將結果寫入高速緩存中,最後寫入主存中,此時主存i==2,線程B作一樣的操做,主存中的i仍然=2。因此最終結果爲2並非3。這種現象就是緩存一致性問題。
解決緩存一致性方案有兩種:
經過在總線加LOCK#鎖的方式;
經過緩存一致性協議。
可是方案1存在一個問題,它是採用一種獨佔的方式來實現的,即總線加LOCK#鎖的話,只能有一個CPU可以運行,其餘CPU都得阻塞,效率較爲低下。
第二種方案,緩存一致性協議(MESI協議)它確保每一個緩存中使用的共享變量的副本是一致的。因此JMM就解決這個問題。
有volatile修飾的共享變量進行寫操做的時候會多出Lock前綴的指令,該指令在多核處理器下會引起兩件事情。
將當前處理器緩存行數據刷寫到系統主內存。
這個刷寫回主內存的操做會使其餘CPU緩存的該共享變量內存地址的數據無效。
這樣就保證了多個處理器的緩存是一致的,對應的處理器發現本身緩存行對應的內存地址被修改,就會將當前處理器緩存行設置無效狀態,當處理器對這個數據進行修改操做的時候會從新從主內存中把數據讀取到緩存裏。
volatile常常用於兩個場景:狀態標記、double check
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
複製代碼
這段代碼是很典型的一段代碼,不少人在中斷線程時可能都會採用這種標記辦法。可是事實上,這段代碼會徹底運行正確麼?即必定會將線程中斷麼?不必定,也許在大多數時候,這個代碼可以把線程中斷,可是也有可能會致使沒法中斷線程(雖然這個可能性很小,可是隻要一旦發生這種狀況就會形成死循環了)。
下面解釋一下這段代碼爲什麼有可能致使沒法中斷線程。在前面已經解釋過,每一個線程在運行過程當中都有本身的工做內存,那麼線程1在運行的時候,會將stop變量的值拷貝一份放在本身的工做內存當中。
那麼當線程2更改了stop變量的值以後,可是還沒來得及寫入主存當中,線程2轉去作其餘事情了,那麼線程1因爲不知道線程2對stop變量的更改,所以還會一直循環下去。
可是加上volatile就沒問題了。以下所示:
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
複製代碼
public 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;
}
}
複製代碼
客官以爲有用請點贊或收藏,關注公衆號JavaStorm,你將發現一個有趣的靈魂!
後面咱們繼續分析JMM內存模型相關技術。
將本身的知識分享,之後會持續輸出,但願給讀者朋友們帶來幫助。如有幫助讀者朋友能夠點贊或者關注。