1、volatile變量語義一的概念java
當一個變量被定義成volatile以後,具有兩個特性:緩存
特性一:保證此變量對全部線程的可見性。這裏的「可見性」是指當一條線程修改了這個變量的值,新值對於其餘線程來講是能夠當即得知的。而普通變量並不能作到這一點,普通變量的值在線程傳遞時均須要經過主內存來完成。 好比:線程A修改了一個普通變量的值,而後向主內存進行回寫,另外一條線程B在線程A回寫完成了以後再對主內存進行讀取操做,新變量值纔會對線程B可見。安全
2、volatile可以保證線程安全嗎併發
基於volatile變量在各個線程中是不存在一致性問題的,從物理存儲的角度看,各個線程的工做內存中volatile變量也能夠存在不一致的狀況,可是因爲每次使用前都要進行刷新,執行引擎看不到不一致的狀況,所以也能夠人爲不存在一致性問題,可是java裏面的運算操做符並不是是原子操做,這致使了volatile變量的運算在併發下同樣是不安全的。ide
案例代碼:測試
/** * 測試Volatile的特性 */ public class VolatileTest { public static volatile int race = 0; public static void increase(){ race++; } //定義線程的數量 private static final int THREADS_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for(int i = 0;i<THREADS_COUNT;i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for(int j = 0;j<1000;j++){ increase(); } } }); threads[i].start(); System.out.println("線程"+i+"開始執行"); } while (Thread.activeCount()>2){ System.out.println("Thread.activeCount() = "+Thread.activeCount()); Thread.yield();//有其餘線程等待時,將該線程設置爲就緒狀態。 } System.out.println("race:"+race); } }
這段代碼發起了20個線程,每一個線程都對race變量的作了10000次的自增操做,若是是正常的併發的話,那麼race的結果用該是200000,但是執行幾回,發現結果並非200000,而都是一個小於200000的值。這是爲何呢? 由於++操做自己就不是原子的,要通過讀取計算和寫回,那麼,咱們經過一張圖模仿一下以上代碼:優化
因爲變量被volatile修飾,所以這張圖中的3,4操做是連續不間斷的,5,6,7的操做也是連續不間斷的,可是通過兩個線程的讀取修改寫回操做後,i的值僅僅從1變爲了2,並非咱們想象的3,spa
可能在這裏理解上述的圖和描述有點抽象,由於有的朋友可能並不能理解數據在主存和緩存中的讀取更改的傳遞規則,在這裏,補充一下變量在內存之間的相互操做知識點,你們能夠先看如下這塊內容,再回過頭進行理解上述圖中的操做。線程
三:內存間的相互操做3d
·lock(鎖定):做用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態。
·unlock(解鎖):做用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
·read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用。
·load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。
·use(使用):做用於工做內存的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用變量的值的字節碼指令時將會執行這個操做。
·assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
·store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨後的write操做使用。
·write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的變量的值放入主內存的變量中。
由volatile修飾的變量的特性保證此線程的可見性可知,當咱們使用volatile修飾了一個變量後,一個線程對此變量的修改對於其餘線程來說是當即可知的,也就是說.assign,.store,.write這三個操做是原子的,中間不會間斷,會立刻的同步主存,就像直接操做主存同樣,並經過緩存一致性通知其餘的緩存中的副本過時。普通變量可能會在.assign,.store,.write這三個操做中插入其餘的操做,致使更改後的數據不能當即同步回主存,這種狀況在volatile修飾變量時是不存在的。
四:使用volatile控制併發的場景
因爲volatile變量只能保證可見性,在不符合如下兩條規則的運算場景中,咱們仍然要經過加鎖(使用synchronized、java.util.concurrent中的鎖或原子類)來保證原子性:
一、運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。
二、變量不須要與其餘的狀態變量共同參與不變約束。
舉個例子:
class VolatileOne{ volatile boolean isShutDown; public void shutDown(){ isShutDown = true; } public void dowork(){ while (!isShutDown){ //業務代碼 } } }
這類場景就比較適合使用volatile控制併發,當 shutDown()方法被調用時,能保證全部線程中執行的dowork()方法都當即停下來。
使用volatile變量的第二個特性是禁止指令重排優化,咱們下一篇再來分析。
補充一下,本文參考資料來源於:《深刻理解Java虛擬機:JVM高級特性與最佳實踐(第3版)》,其中講解race++操做時書上用了字節碼進行解釋的,我這裏經過搜索資料,經過一張圖來進行的描述,但願能夠直觀的幫助你們理解。
還有,昨天寫的音樂+技術+逗比的結合寫法被朋友說寫的太亂了,很無厘頭,所以這篇中規中矩的寫了一篇,整理加自身理解,做者整理不易,以爲能幫助到您的請動動小手點個贊,若有問題請評論區提出。