Volatile
Java語言的關鍵字,用來聲明變量,表示這個變量是被同時運行的幾個線程修改的,需要保證變量的可見性並禁止指令重排序。
可見性的含義:
可見性的意思是指當一個線程修改一個共享變量時,另外一個線程能讀到這個修改後的值,volatile作用就是當一個線程修改了共享變量的值,其他線程馬上就能知道。
Java內存模型規定所有的變量都是存在主存當中,每個線程都有自己的工作內存。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存
那麼Volatile是如何來保證可見性的呢?
在x86處理器下通過工具生成的彙編指令來看看對Volatile進行寫操作時,CPU會做什麼事兒:
Java代碼:
instance = new Singleton();//其中instance是volatile修飾的變量
彙編代碼:
0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);
lock前綴指令實際上相當於一個內存屏障,會提供3個功能:
1.它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
2.它會強制將對緩存的修改操作立即寫入主存;
3.如果是寫操作,它會導致其他CPU中對應的緩存行無效。
處理器為了提高處理速度,不直接和內存進行通訊,而是先將系統內存的數據讀到內部緩存,後再進行操作,但操作完之後不知道何時會寫到內存,如下圖1所示:

如果對聲明了Volatile變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。
但是就算寫回到內存,如果其他處理器緩存的值還是舊的,所以在多處理器下,為了保證各個處理器的緩存是一致的,硬體層面就會實現緩存一致性協議(每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是已經不是最新的,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態),線程再處理該數據時,就會強制重新從內存裡把數據讀到處理器緩存裡,如圖2。

指令重排序:
請看如下代碼:
x = 1; //語句1
y = 1; //語句2
flag = true; //語句3
x = x+1; //語句4
y = y-1; //語句5
在執行過程中,編譯器可能會自行安排執行順序,讓語句執行的更快。
在單線程情況下,編譯器能保證語句4在語句1之後,語句5在語句2之後,因為兩者之間有依賴關係,而結果是可以確定的。
在多線程時,因為多個緩存和線程同時處理,就難以保證結果了,可能語句4排在語句1之前執行,導致結果變化。而設置flag為Volatile類型的參數,那麼在多線程情況下,語句4也一定在語句1之後,語句5在語句2之後,重排序只存在語句4和5,語句1和語句2之間,不能跨越語句3進行重排序