大白話聊聊Java併發面試問題之volatile究竟是什麼?【石杉的架構筆記】

歡迎關注我的公衆號:石杉的架構筆記(ID:shishan100)java

週一至週五早8點半!精品技術文章準時送上!面試

1、寫在前面

前段時間把幾年前帶過的一個項目架構演進的過程整理了一個系列出來,參見(億級流量架構系列專欄總結)。算法

不過不少同窗看了以後,後臺反饋說文章太燒腦,看的雲裏霧裏。其實這個也正常,文章承載的信息畢竟有限,而架構的東西細節太多,想要僅僅經過文章看懂一個系統架構的設計和落地,確實難度不小。編程

因此接下來用大白話跟你們聊點輕鬆的話題,比較易於理解,並且對你們工做和麪試都頗有幫助。緩存

2、場景引入,問題初現

不少同窗出去面試,都會被問到一個常見的問題:說說你對volatile的理解?安全

很多初出茅廬的同窗可能會有點措手不及,由於可能就是以前沒關注過這個。可是網上百度一下呢,很多文章寫的很好,可是理論扎的太深,文字太多,圖太少,讓人有點難以理解。性能優化

基於上述痛點,這篇文章嘗試站在年輕同窗的角度,用最簡單的大白話,加上多張圖給你們說一下,volatile究竟是什麼?多線程

固然本文不會把理論扎的太深,由於一會兒扎深了文字太多,不少同窗仍是會很差理解。架構

本文僅僅是定位在用大白話的語言將volatile這個東西解釋清楚,而涉及到特別底層的一些原理和技術問題,之後有機會開文再寫。併發

首先,給你們上一張圖,我們來一塊兒看看:

如上圖,這張圖說的是java內存模型中,每一個線程有本身的工做內存,同時還有一個共享的主內存。

舉個例子,好比說有兩個線程,他們的代碼裏都須要讀取data這個變量的值,那麼他們都會從主內存里加載data變量的值到本身的工做內存,而後纔可使用那個值。

好了,如今你們從圖裏看到,每一個線程都把data這個變量的副本加載到了本身的工做內存裏了,因此每一個線程均可以讀到data = 0這個值。

這樣,在線程代碼運行的過程當中,對data的值均可以直接從工做內存里加載了,不須要再從主內存里加載了。

那問題來了,爲啥必定要讓每一個線程用一個工做內存來存放變量的副本以供讀取呢?我直接讓線程每次都從主內存加載變量的值不行嗎?

很簡單!由於線程運行的代碼對應的是一些指令,是由CPU執行的!可是CPU每次執行指令運算的時候,也就是執行咱們寫的那一大坨代碼的時候,要是每次須要一個變量的值,都從主內存加載,性能會比較差!

因此說後來想了一個辦法,就是線程有工做內存的概念,相似於一個高速的本地緩存。

這樣一來,線程的代碼在執行過程當中,就能夠直接從本身本地緩存里加載變量副本,不須要從主內存加載變量值,性能能夠提高不少!

可是你們思考一下,這樣會有什麼問題?

咱們來設想一下,假如說線程1修改了data變量的值爲1,而後將這個修改寫入本身的本地工做內存。那麼此時,線程1的工做內存裏的data值爲1。

然而,主內存裏的data值仍是爲0!線程2的工做內存裏的data值仍是0啊?!

這可尷尬了,那接下來,在線程1的代碼運行過程當中,他能夠直接讀到data最新的值是1,可是線程2的代碼運行過程當中讀到的data的值仍是0!

這就致使,線程1和線程2其實都是在操做一個變量data,可是線程1修改了data變量的值以後,線程2是看不到的,一直都是看到本身本地工做內存中的一箇舊的副本的值!

這就是所謂的java併發編程中的可見性問題

多個線程併發讀寫一個共享變量的時候,有可能某個線程修改了變量的值,可是其餘線程看不到!也就是對其餘線程不可見!

3、volatile的做用及背後的原理

那若是要解決這個問題怎麼辦呢?這時就輪到volatile閃亮登場了!你只要給data這個變量在定義的時候加一個volatile,就直接能夠完美的解決這個可見性的問題。

好比下面的這樣的代碼,在加了volatile以後,會有啥做用呢?

完整的做用就不給你們解釋了,由於咱們定位就是大白話,要是把底層涉及的各類內存屏障、指令重排等概念在這裏帶出來,很多同窗又要蒙圈了!

咱們這裏,就說說他最關鍵的幾個做用是啥?

第一,一旦data變量定義的時候前面加了volatile來修飾的話,那麼線程1只要修改data變量的值,就會在修改完本身本地工做內存的data變量值以後,強制將這個data變量最新的值刷回主內存,必須讓主內存裏的data變量值立馬變成最新的值!

整個過程,以下圖所示:

第二,若是此時別的線程的工做內存中有這個data變量的本地緩存,也就是一個變量副本的話,那麼會強制讓其餘線程的工做內存中的data變量緩存直接失效過時,不容許再次讀取和使用了!

整個過程,以下圖所示:

第三,若是線程2在代碼運行過程當中再次須要讀取data變量的值,此時嘗試從本地工做內存中讀取,就會發現這個data = 0已通過期了!

此時,他就必須從新從主內存中加載data變量最新的值!那麼不就能夠讀取到data = 1這個最新的值了!整個過程,參見下圖:

bingo!好了,volatile完美解決了java併發中可見性的問題!

對一個變量加了volatile關鍵字修飾以後,只要一個線程修改了這個變量的值,立馬強制刷回主內存。

接着強制過時其餘線程的本地工做內存中的緩存,最後其餘線程讀取變量值的時候,強制從新從主內存來加載最新的值!

這樣就保證,任何一個線程修改了變量值,其餘線程立馬就能夠看見了!這就是所謂的volatile保證了可見性的工做原理!

4、總結 & 提醒

最後給你們提一嘴,volatile主要做用是保證可見性以及有序性。

有序性涉及到較爲複雜的指令重排、內存屏障等概念,本文沒說起,可是volatile是不能保證原子性的

也就是說,volatile主要解決的是一個線程修改變量值以後,其餘線程立馬能夠讀到最新的值,是解決這個問題的,也就是可見性!

可是若是是多個線程同時修改一個變量的值,那仍是可能出現多線程併發的安全問題,致使數據值修改錯亂,volatile是不負責解決這個問題的,也就是不負責解決原子性問題!

原子性問題,得依賴synchronized、ReentrantLock等加鎖機制來解決


END



若有收穫,請幫忙轉發,您的鼓勵是做者最大的動力,謝謝!


一大波微服務、分佈式、高併發、高可用的原創系列文章正在路上

歡迎掃描下方二維碼,持續關注:


石杉的架構筆記(id:shishan100)

十餘年BAT架構經驗傾囊相授


推薦閱讀:

一、拜託!面試請不要再問我Spring Cloud底層原理

二、【雙11狂歡的背後】微服務註冊中心如何承載大型系統的千萬級訪問?

三、【性能優化之道】每秒上萬併發下的Spring Cloud參數優化實戰

四、微服務架構如何保障雙11狂歡下的99.99%高可用

五、兄弟,用大白話告訴你小白都能聽懂的Hadoop架構原理

六、大規模集羣下Hadoop NameNode如何承載每秒上千次的高併發訪問

七、【性能優化的祕密】Hadoop如何將TB級大文件的上傳性能優化上百倍

八、拜託,面試請不要再問我TCC分佈式事務的實現原理坑爹呀!

九、【坑爹呀!】最終一致性分佈式事務如何保障實際生產中99.99%高可用?

十、拜託,面試請不要再問我Redis分佈式鎖的實現原理!

十一、【眼前一亮!】看Hadoop底層算法如何優雅的將大規模集羣性能提高10倍以上?

十二、億級流量系統架構之如何支撐百億級數據的存儲與計算

1三、億級流量系統架構之如何設計高容錯分佈式計算系統

1四、億級流量系統架構之如何設計承載百億流量的高性能架構

1五、億級流量系統架構之如何設計每秒十萬查詢的高併發架構

1六、億級流量系統架構之如何設計全鏈路99.99%高可用架構

1七、七張圖完全講清楚ZooKeeper分佈式鎖的實現原理

相關文章
相關標籤/搜索