volatile
是用來標記一個JAVA變量存儲在主內存(main memory)
中,多線程讀寫volatile變量會先從高速緩存中讀取,可是寫入的時候會當即經過內存總線刷到主存,同時內存總線中會對這個變量進行監聽,當發現數據變更時,會主動將該變量的CPU Cache置爲失效。確切的說:每次寫操做volatile變量
時,將直接將主內存(main memory)
中最新的值讀取到當前Cache
操做html
<!-- more -->java
可見性: 是指線程之間數據可見共享,一個線程修改的狀態對另外一個線程是可見的。好比:用volatile修飾的變量
,就會確保變量在修改時,其它線程是可見的。。git
在多線程中,對非volatile變量
進行操做的時候,出於對性能的考慮,當對這些變量進行數據操做時,線程可能會從主內存裏拷貝變量到CPU Cache中去。多核CPU環境中,多個線程分別在不一樣的CPU中運行,就意味着,多個線程都有可能將變量拷貝到當前運行的CPU Cache裏。緩存
以下圖所示(多線程數據模型): 微信
public class NotSharedObject { private static int COUNTER = 0; private static final int MAX_LIMIT = 5; public static void main(String[] args) { new Thread(() -> { int localValue = COUNTER; while (localValue < MAX_LIMIT) { if (localValue != COUNTER) { System.out.printf("[線程] - [%s] - [%d]\n", Thread.currentThread().getName(), COUNTER); localValue = COUNTER; } } }, "READER").start(); new Thread(() -> { int localValue = COUNTER; while (COUNTER < MAX_LIMIT) { System.out.printf("[線程] - [%s] - [%d]\n", Thread.currentThread().getName(), ++localValue); COUNTER = localValue; } }, "UPDATER").start(); } }
[線程] - [UPDATER] - [1] [線程] - [UPDATER] - [2] [線程] - [UPDATER] - [3] [線程] - [UPDATER] - [4] [線程] - [UPDATER] - [5]
結果代表,UPDATE
線程雖修改數據,可是READER
線程並未監聽到數據的變更,當前線程操做的是當前CPU Cache
裏的數據,而不是從main memory
獲取的。多線程
couner 的變量未使用volatile關鍵字修飾
,即JVM沒法保證有效的將CPU Cache
的內容寫入主存中。意味着 counter 變量在CPU Cache
中的值可能會與主存中的值不同。app
以下圖所示(無Volatile): 性能
private static volatile int COUNTER = 0; [線程] - [UPDATER] - [1] [線程] - [UPDATER] - [2] [線程] - [READER] - [1] [線程] - [UPDATER] - [3] [線程] - [UPDATER] - [4] [線程] - [UPDATER] - [5] [線程] - [READER] - [3]
結果代表,volatile修飾後
的變量並不會達到Lock
的效果,它只會保證線程可見性,但不保證原子性
,在讀取volatile變量
和寫入它的新值時,因爲操做耗時較短,就會產生 競爭條件:多個線程可能會讀取到volatile變量的相同值,而後產生新值並寫入主內存,這樣將會覆蓋互相的值。(有興趣的能夠在建立一個UPDATE
線程測試效果)測試
以下圖所示(Volatile): 優化
從Java5
以後volatile關鍵字
不只能用於保證變量從主存中進行讀寫操做,同時還遵循Happens-Before原則
,下文將會描述存在於volatile
中的一些細節,想深刻的能夠自行谷歌 happens-before relationship
或者訪問提供的幾個連接
參考文獻(1):https://en.wikipedia.org/wiki/Happened-before
參考文獻(2):http://preshing.com/20130702/the-happens-before-relation/
參考文獻(3):http://www.importnew.com/17149.html
若是T1線程
寫入了一個volatile變量
而後T2線程
讀取該變量,那麼T1線程
寫以前對其可見的全部變量,T2線程
讀取該volatile
以後也會對其可見。
禁止JVM指令重排優化,一旦被volatile
修飾的變量,賦值後多執行了一個load addl $0x0, (%esp)
操做,至關於多了一個內存屏障(指令重排序時不能把後面的指令重排序到內存屏障以前的位置),單核CPU訪問內存時,並不須要內存屏障;
看看下面這個示例:
T1線程: Object obj; volatile boolean init; ---------T1線程------------ obj = createObj() 1; init = true; 2; ---------T2線程------------ while(!init){ sleep(); } useTheObj(obj);
被volatile修飾過的變量 init
在寫操做以前,建立了非volatile變量的obj
,於是T1線程
在寫入init
後,會將obj
也寫入主內存中去。
因爲T2線程
啓動的時候讀取被volatile修飾過的init,於是變量 init
和變量 obj
都會被寫入T2線程
所使用的CPU緩存中去。當T2線程
讀取 obj
變量時,它將能看見被T1線程
寫入的東西。
線程可見,狀態量標記
volatile boolean start = true; while(start){ // } void close(){ start = false; }
屏障先後一致性,禁止指令重排
我的QQ:1837307557
battcn開源羣(適合新手):391619659
微信公衆號:battcn
(歡迎調戲)