再有人問你volatile是什麼,就把這篇文章發給他,讓他啞口無言
本文就圍繞volatile展開,主要介紹volatile的用法、volatile的原理,以及volatile是如何提供可見性和有序性保障的等。
volatile這個關鍵字,不僅僅在Java語言中有,在很多語言中都有的,而且其用法和語義也都是不盡相同的。尤其在C語言、C++以及Java中,都有volatile關鍵字。都可以用來宣告變數或者物件。下面簡單來介紹一下Java語言中的volatile關鍵字。
volatile的用法
volatile通常被比喻成"輕量級的synchronized",也是Java併發程式設計中比較重要的一個關鍵字。和synchronized不同,volatile是一個變數修飾符,只能用來修飾變數。無法修飾方法及程式碼塊等。
volatile的用法比較簡單,只需要在宣告一個可能被多執行緒同時訪問的變數時,使用volatile修飾就可以了。
如以上程式碼,是一個比較典型的使用雙重鎖校驗的形式實現單例的,其中使用volatile關鍵字修飾可能被多個執行緒同時訪問到的singleton。
volatile的原理
為了提高處理器的執行速度,在處理器和記憶體之間增加了多級快取來提升。但是由於引入了多級快取,就存在快取資料不一致問題。
但是,對於volatile變數,當對volatile變數進行寫操作的時候,JVM會向處理器傳送一條lock字首的指令,將這個快取中的變數回寫到系統主存中。
但是就算寫回到記憶體,如果其他處理器快取的值還是舊的,再執行計算操作就會有問題,所以在多處理器下,為了保證各個處理器的快取是一致的,就會實現快取一致性協議
快取一致性協議:每個處理器通過嗅探在匯流排上傳播的資料來檢查自己快取的值是不是過期了,當處理器發現自己快取行對應的記憶體地址被修改,就會將當前處理器的快取行設定成無效狀態,當處理器要對這個資料進行修改操作的時候,會強制重新從系統記憶體裡把資料讀到處理器快取裡。
所以,如果一個變數被volatile所修飾的話,在每次資料變化之後,其值都會被強制刷入主存。而其他處理器的快取由於遵守了快取一致性協議,也會把這個變數的值從主存載入到自己的快取中。這就保證了一個volatile在併發程式設計中,其值在多個快取中是可見的。
volatile與可見性
可見性是指當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。
Java記憶體模型規定了所有的變數都儲存在主記憶體中,每條執行緒還有自己的工作記憶體,執行緒的工作記憶體中儲存了該執行緒中是用到的變數的主記憶體副本拷貝,執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體。不同的執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數的傳遞均需要自己的工作記憶體和主存之間進行資料同步進行。所以,就可能出現執行緒1改了某個變數的值,但是執行緒2不可見的情況。
前面的關於volatile的原理中介紹過了,Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變數在被修改後可以立即同步到主記憶體,被其修飾的變數在每次是用之前都從主記憶體重新整理。因此,可以使用volatile來保證多執行緒操作時變數的可見性。
volatile與有序性
除了引入了時間片以外,由於處理器優化和指令重排等,CPU還可能對輸入程式碼進行亂序執行,比如load->add->save 有可能被優化成load->save->add 。這就是可能存在有序性問題。
而volatile除了可以保證資料的可見性之外,還有一個強大的功能,那就是他可以禁止指令重排優化等。
普通的變數僅僅會保證在該方法的執行過程中所依賴的賦值結果的地方都能獲得正確的結果,而不能保證變數的賦值操作的順序與程式程式碼中的執行順序一致。
volatile可以禁止指令重排,這就保證了程式碼的程式會嚴格按照程式碼的先後順序執行。這就保證了有序性。被volatile修飾的變數的操作,會嚴格按照程式碼順序執行,load->add->save 的執行順序就是:load、add、save。
volatile與原子性
執行緒是CPU排程的基本單位。CPU有時間片的概念,會根據不同的排程演算法進行執行緒排程。當一個執行緒獲得時間片之後開始執行,在時間片耗盡之後,就會失去CPU使用權。所以在多執行緒場景下,由於時間片線上程間輪換,就會發生原子性問題。
,為了保證原子性,需要通過位元組碼指令monitorenter和monitorexit,但是volatile和這兩個指令之間是沒有任何關係的。
所以,volatile是不能保證原子性的。
在以下兩個場景中可以使用volatile來代替synchronized:
1、運算結果並不依賴變數的當前值,或者能夠確保只有單一的執行緒會修改變數的值。
2、變數不需要與其他狀態變數共同參與不變約束。
除以上場景外,都需要使用其他方式來保證原子性,如synchronized或者concurrent包。
我們來看一下volatile和原子性的例子:
以上程式碼比較簡單,就是建立10個執行緒,然後分別執行1000次i++操作。正常情況下,程式的輸出結果應該是10000,但是,多次執行的結果都小於10000。這其實就是volatile無法滿足原子性的原因。
為什麼會出現這種情況呢,那就是因為雖然volatile可以保證i在多個執行緒之間的可見性。但是無法保證i++的原子性。
i++操作,一共有三個步驟:load i ,add i ,save i。在多執行緒場景中,如果這三個步驟無法按照順序執行的話,那麼就會出現問題。
如上圖,兩個執行緒同時執行i++操作,如果允許指令重排,我們期望的結果是3,但是實際執行結果可能是2,甚至可能是1。
總結與思考
我們介紹過了volatile關鍵字和synchronized關鍵字。現在我們知道,synchronized可以保證原子性、有序性和可見性。而volatile卻只能保證有序性和可見性。
那麼,我們再來看一下雙重校驗鎖實現的單例,已經使用了synchronized,為什麼還需要volatile?
相關文章
- 再有人問你volatile是什麼,就把這篇文章發給他
- 再有人問你synchronized是什麼,就把這篇文章發給他。synchronized
- 再有人問你synchronized是什麼,就把這篇文章發給他synchronized
- 再有人問你Java記憶體模型是什麼,就把這篇文章發給他。Java記憶體模型
- 再有人問你Java記憶體模型是什麼,就把這篇文章發給他Java記憶體模型
- 如果有人再問你 Java 的反射,把這篇文章扔給他Java反射
- 再有人問你分散式鎖,這篇文章扔給他分散式
- 如果再有人問你分散式 ID,這篇文章丟給他分散式
- 還有人不懂分散式鎖的實現就把這篇文章丟給他分散式
- 以後有面試官問你跳躍表,你就把這篇文章扔給他面試
- 以後有面試官問你「跳躍表」,你就把這篇文章扔給他面試
- 如果有人再問你怎麼實現分散式延時訊息,這篇文章丟給他分散式
- 面試官問你B樹和B+樹,就把這篇文章丟給他面試
- 如果有人給你撕逼Java記憶體模型,就把這些問題甩給他Java記憶體模型
- 【漫畫】以後在有面試官問你AVL樹,你就把這篇文章扔給他。面試
- 【漫畫】以後在有面試官問你平衡(AVL)樹,你就把這篇文章扔給他。面試
- Spring註解驅動開發第16講——面試官再問你BeanPostProcessor的執行流程,就把這篇文章甩給他!Spring面試Bean
- 再有人問你分散式事務,把這篇扔給他分散式
- 面試官問你MyBatis SQL是如何執行的?把這篇文章甩給他面試MyBatisSQL
- 誰再說學不會 MySQL 資料庫,就把這個給他扔過去!MySql資料庫
- 以後再有人說程式設計師懶,請把這篇文章給他看!程式設計師
- 面試官問:“在專案中用過多執行緒嗎?”你就把這個案例講給他聽!面試執行緒
- 面試還問這些?甩手就給個讓他僵住的答案!面試
- IPTV是什麼意思-2020年還有人再問IPTV是什麼
- 不要問程式設計師什麼是“物件”,也不要給他介紹“物件”程式設計師物件
- 你必須讓他停下
- 還不夠他發的捨得讓他發給使用者及庫很有用
- 程式語言簡史:有人不喜歡花括號,於是他發明了PythonPython
- 當面試官問你這個問題的時候,他想聽到什麼?面試
- 再說深度學習是黑匣子,就把這篇文章糊 Ta 臉上深度學習
- 新員工一口氣寫完了這些C語言例子,領導給他轉正了!C語言
- 面試官:Dubbo是什麼,他有什麼特性?面試
- 為了讓你知道自己是哪種垃圾,他們做了這些遊戲遊戲
- 面試官問你基本型別時他想知道什麼面試型別
- 當面試官問你Vue響應式原理,你可以這麼回答他面試Vue
- RAKsmart高防伺服器的優勢是什麼?這篇文章給你答案伺服器
- 你知道什麼是後端套模板嗎?他們是怎麼操作的知道嗎?後端
- 什麼是NoSQL?這裡給你整明白SQL