Java併發之volatile究竟是什麼?

 

  • 場景引入,問題初現

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

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

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

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

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

    

        

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

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

        好了,如今你們從圖裏看到,每一個線程都把data這個變量的副本加載到了本身的工做內存裏了,因此每一個線程均可以讀到data = 0這個值。這樣,在線程代碼運行的過程當中,對data的值均可以直接從工做內存里加載了,不須要再從主內存里加載了。spa

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

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

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

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

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

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

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

        

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

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

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

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

  • volatile的做用及背後的原理

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

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

        

            

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

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

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

            整個過程,以下圖所示:

            

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

         整個過程,以下圖所示:

        

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

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

        

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

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

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

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

  • 總結 & 提醒

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

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

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

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

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

相關文章
相關標籤/搜索