volatile是java併發編程中修飾類的成員變量、成員屬性或者對象的一個關鍵字。是java併發編程中最輕量級的併發實現,保證所修飾的變量對多個線程內存可見。在一個線程寫,多個線程讀的場景下,首選使用volatile關鍵字。java
此處用一個線程安全的單例模式來講明volatile具體的使用場景和原理,首先,線程安全的單例模式是不少面試官都喜歡考察面試者的一個問題,網絡上各類博客都談到餓漢式,懶漢式單例模式的編寫方式,也提到過一個雙重檢查模式的單例模式,雙重檢查單例模式代碼以下:面試
相信不少人認爲這已是線程安全的單例模式了,然而這真的是線程安全的嗎?其實不是,爲何呢?緣由其實很簡單,若是兩個線程調用getInstance()方法的時候,其中某一個線程正在執行第一重檢查,另一個線程正在執行第二重檢查中的instance = new Singlton()的時候,此時這句實例化的代碼在咱們看來只有一句,但翻譯成cpu能運行的指令的時候會變成三句:(1)給instance分配一個棧指針,(2)給Singlton對象分配堆內存,(3)將棧指針指向堆內存。那麼cpu在作這三步操做的時候執行順序會重排序(cpu按本身的規則從新排序後執行),即執行順序多是(1)(3)(2),那麼問題就來了,當執行(1)後,假如第一個線程正在執行第一重檢查,那麼此時會發現instance並不爲空,就直接返回了。然而實際對象分配堆內存還沒完成,此時就會形成instance只是一個空的指針。當咱們調用Singlton對象中的成員變量時,就會出錯。問題出來了,那麼解決方法很是簡單,就是使用該文章的主題--volatile關鍵字,在咱們對象成員變量上加上volatile便可。編程
private static volatile Singlton instance ;緩存
要理解透徹volatile的原理須要明白cpu指令重排序、內存屏障以及線程的本地緩存和主內存等相關知識。指令重排序:以上的示例也說明了cpu在執行一條咱們編寫的代碼的時候,首先會編譯成彙編語言,最終編譯成二進制指令後在cpu的寄存器上進行存取並執行。即一條代碼會變成n條指令,cpu執行這n條指令的時候,不會老老實實的按順序進行執行,而是會按必定的規則(多是先簡單後複雜,
先加減後乘除等等)從新排序後執行。這就是指令重排序。重排序的類型有編譯器優化重排序,指令級並行重排序,內存系統重排序。
內存屏障:這是針對併發下重排序帶來的問題的一種解決方案,現代cpu都有的一種內存屏障類型是:StoreLoad Barriers,另外還有三種分別是:LoadLoad Barriers、StoreStore Barriers以及LoadStore Barriers.
線程的本地緩存與主內存:
(1)、每一個線程都會有本身的本地緩存,如上圖所示,線程A,線程B都會有本身的本地緩存,用來緩存線程自己所須要使用的數據。
(2)、正常狀況下,當線程A改變了本身本地緩存中的a變量的值,並不會立刻刷新到主內存,同理,線程B也同樣。
(3)、若是給a變量加上volatile修飾,那麼當線程A改變了本地緩存中a的值後,cpu會強制將線程A的本地緩存中的a變量的值刷新到主內存,即讓主內存的a的值與線程A的本地緩存中的a值一致。且同時讓線程B中的本地緩存中的a失效,那麼當線程B要使用本地緩存中的a變量的時候,會從新到主內存讀取並刷新本身的本地緩存。這就是volatile的內存語義。而後回頭解釋案例中爲什麼加入volatile修飾就保證了真正的線程安全,直接來講就是一句話:volatile修飾的變量在cpu底層執行的時候插入了相關的內存屏障禁止指令進行重排序。具體來講是在volalite寫操做前插入StoreStore屏障,寫操做後面插入StoreLoad屏障,在volatile讀操做的後面連續插入LoadLoad屏障和LoadStore屏障。
總結:volatile關鍵字修飾的變量具備內存可見性,對一個volatile變量的讀,老是能看到(任意線程)對這個變量最後的寫入。volatile的併發實現是利用cpu的lock前綴指令對消息總線進行加鎖,同時讓volatile當前所在線程的本地緩存內容強行刷進主內存,並讓其餘使用了該變量的本地緩存失效,當其餘線程須要使用volatile所修飾的變量的時候就會從新到主內存中讀取。
本文爲博主原創文章,歡迎留言交流,轉載請註明原文出處。
java開發工具下載地址及安裝教程大全,點這裏。
更多技術文章,在這裏。