在Java相關的崗位面試中,不少面試官都喜歡考察面試者對Java併發的瞭解程度,而以volatile關鍵字做爲一個小的切入點,每每能夠一問到底,把Java內存模型(JMM),Java併發編程的一些特性都牽扯出來,深刻地話還能夠考察JVM底層實現以及操做系統的相關知識。面試
- 本文以一次假想的面試過程,來深刻了解下volitile關鍵字【本菜雞還沒投簡歷,準備好被今年20年秋招的毒打了😭】
- 題目有標題黨的嫌疑,可是若是你們好好理解文中涉及到的兩篇文章,相信你對volatile有更深入的認識!
【靈魂拷問開始】編程
到這裏,個人眼裏已經是常含淚水了。不是由於我對代碼愛的深沉,而是由於我菜的真誠!緩存
沒事,不就是個破volatile
嗎?別念了,我學習還不行嗎!併發
volatile
是JVM提供的輕量級的同步機制app
保證可見性post
保證有序性,禁止指令重排學習
不保證原子性(須要藉助synchronized或者CAS)操作系統
小夥子,不錯麼😊,回答對了 1 + 1,得來點 2 + 2 的難度了線程
問題2和問題3講到的可見性,用JMM來解釋的話,本質上是同一個問題。至於有序性和重排序,到問題4再討論。3d
一問到內存的可見性,volatile
相關的,直接就把JMM內存模型搬出來好吧。先放圖,而後再表演。
如下是俺我的的回答,有不足或漏洞,歡迎你們更正指出!
所謂可見性,是指當一條線程修改了共享變量的值,新值對於其餘線程來講是能夠當即得知的
每一個線程都有本身獨立的工做區間,爲了匹配CPU的運行速度,他們不會直接從內存中讀取數據,而是將數據拷貝一份到CPU緩存中(即每一個線程本身的工做內存),他們之間的相互交互,是經過內存來完成的。
根據JMM內存模型的8大原子操做,每一個線程在j將數據操做完stroe
回主存以前,會加lock
指令來鎖定內存區域的緩存(緩存行鎖定),根據MESI緩存一致性協議,總線經過偵聽器發現數據被修改,會當即讓其餘線程工做內存中不一致的副本當即失效。
等到當前線程將更改後的數據write
回主存後,當即執行unlock
指令。
此時,其餘線程再從新讀取更新後的數據,再拷貝到本身的工做內存。總線偵聽機制會在總線上檢測線程的數據,發現有線程作了更改時準備store
回主內存時,它就會馬上將其餘線程工做內存中的副本置位無效,而後重新到主存獲取更新後的值。
除了使用 volatile 關鍵字來保證內存可見性以外,使用synchronized
或Lock
鎖也能保證變量的內存可見性。只是相比而言使用 volatile
關鍵字開銷更小,是輕量級的鎖。
這就是內存可見性的原理。
使用volatile關鍵字修飾共享變量即可以禁止指令重排序。若用volatile修飾共享變量,在JVM底層volatile是採用「內存屏障」來實現禁止特定類型的處理器重排序。加入volatile關鍵字時,會多出一個lock前綴指令,lock前綴指令實際上至關於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經所有完成;
它會強制將對緩存的修改操做當即寫入主存;
若是是寫操做,它會致使其餘CPU中對應的緩存行無效。
JMM具有一些先天的有序性,經過Happens-Before原則就能夠保證的必定的有序性。
volatile
關鍵字不能保證原子性。
a. 當寫一個volatile變量時,JMM會把該線程本地內存中的變量強制刷新到主內存中去;
b. 這個寫會操做會致使其餘線程中的緩存無效。
對於相似i++
這樣的複合操做,要想保證原子性,只能藉助於synchronized
、Lock
以及併發包下的AtomicInteger
的原子操做類。AtomicInteger對基本數據類型的 自增(加1操做),自減(減1操做)、以及加法操做(加一個數),減法操做(減一個數)進行了封裝,保證這些操做是原子性操做。
看完這篇👇文章,這道題目就是你的主場。
看着這張圖,直接就巴拉巴拉一頓操做!