當一個變量定義爲volatile以後,它具有兩種特性:java
保證此變量對全部線程的可見性,這裏的「可見性」是指當一條線程修改了這個變量的值,新值對於其餘線程來講是能夠當即得知的。緩存
禁止指令重排序優化。工具
在X86處理器下經過工具獲取 JIT編譯器生成的彙編指令來看下volatile變量進行讀寫操做時CPU的行爲:
Java 代碼以下:性能
// volatile Object instance; instance = new Singleton();
生成的彙編代碼以下:優化
0x01a3de1d: movb $0X0, 0X1104800(%esi); 0X01a3de24: lock addl $0X0, (%esp);
有volatile變量修飾的共享變量進行寫操做的時候會多出第二行彙編代碼,Lock前綴的指令在多核處理器下會引起了兩件事件。線程
將當前處理器緩存的數據寫回到系統內存。3d
這個寫回內存的操做會使在其餘CPU裏緩存了該內存地址的數據無效。code
再讓咱們從Java內存模型的角度分析下volatile變量。假定T表示一個線程,V和W分別表示兩個volatile變量,那麼在進行read, load, use, assign, store和write時須要知足如下三條規則:排序
只有當線程T對變量V執行的前一個動做是load時,T才能對V執行use; 而且,只有當T對V執行的後一個動做是use時,T才能對V執行load。T對V的use動做能夠認爲是和線程T對V的load,read動做相關聯,必須連續一塊兒出現(這條規則要求 在工做內存中,每次使用V前都必須先從主內存刷新最新的值,用於保證能看見其餘線程對變量V所作的修改後的值)。事件
只有當線程T對變量V執行的前一個動做是assign時,T才能對V執行store動做;而且,只有當T對變量V執行的後一個動做是store時,線程T才能對變量V執行assign動做。線程T對變量V的assign動做可認爲是和線程T對變量V的store, write動做相關聯,必須連續一塊兒出現(這條規則要求 在工做內存中,每次修改V後都必須馬上同步回主內存中,用於保證其餘線程能夠看到本身對變量V所作的修改)。
假定動做A是線程T對變量V實施的use或assign操做,假定動做F是和動做A相關聯的load或store動做,假定動做P是和動做F相應的變量V的read或write動做;相似的,假定動做B是線程T對變量W實施的use或assign動做,假定動做G是和動做B相關聯的load或store動做,假定動做Q是和動做G相應的變量W的read或write動做。若是A先於B,那P先於Q(這條規則要求 volatile修飾的變量不會被指令重排序優化,保證代碼的執行順序與程序的順序相同)。
因爲volatile變量只能保證可見性,在不符合如下兩條規則的運算場景中,仍然要經過加鎖(使用synchronized或java.util.concurrent中的原子類)來保證原子性:
運行結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。
變量不須要與其餘的狀態變量共同參與不變約束。
在某些狀況下,volatile的同步機制性要優於鎖。而且,volatile變量讀操做的性能消耗與普通變量幾乎沒有什麼差異,可是寫操做則可能會慢一些,由於它須要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。