1. 程式人生 > 其它 >volatile關鍵字的原理及其應用

volatile關鍵字的原理及其應用

在某些需要避免併發問題的場景中,我們總能見到volatile的身影,例如在雙重檢查鎖機制下獲取單例項物件:

又比如在Java中的那些基於COW(寫時複製)機制的併發類中,例如CopyOnWriteArrayList:

不能簡單認為使用volatile就能避免併發的問題,對於volatile的原理需要了解,才能應用到正確的使用場景中去。

我們首先要知道,volatile能做什麼,通常來講,我們一般使用volatile來進行執行緒間的變數同步,我們都知道,Java中併發的執行緒都有自己的工作記憶體,各執行緒的工作記憶體是互不可見的,當多個執行緒要訪問某個物件的時候,就會從共享的駐記憶體中複製一份到自己的工作記憶體中,後續對該物件,或者說資源的訪問和讀寫,都各自在自己的工作記憶體中進行,最後寫回到主記憶體中,如果執行緒在執行過程中,已經將該資源複製到工作記憶體中了,但是沒有立刻使用,即使在後面再使用的情況下(只是舉例子,不考慮編譯器優化),也不會再去主記憶體中去獲取最新內容,而被volatile關鍵字修飾的資源,工作執行緒在每次訪問的時候都會從主記憶體中重新整理該資源,這就是我們通常所說的volatile保證記憶體可見性的具體體現,那麼volatile是怎麼做到這一點的呢?如下圖(引用自周志明深入理解JVM第三版,我沒安裝翻譯成組合語言的外掛):

可以看到編譯成的組合語言程式碼中對使用了volatile修飾的變數,在進行操作後有一個add1 $0x0,(%esp)的操作,這個語意是將暫存器的值+0,這裡的關鍵 在於lock字首,查詢IA32手冊可知,它的作用是將本處理器的快取寫入了記憶體,該寫入動作也會引起 別的處理器或者別的核心無效化(Invalidate)其快取,這種操作相當於對快取中的變數“store和write”操作。所以通過這樣一個空操作,讓其他執行緒中快取的值失效,在使用時就必須重新回到主存中獲取該最新值,可讓前面volatile變數的修改對其他處理器立即可見。

另外這句指令也相當於是一個記憶體屏障告訴編譯器不能把後邊的操作放到記憶體屏障之前,指令重排序會帶來什麼問題呢?可以參照這個程式碼示例:

Yesterday You Said Tomorrow