Java併發編程之驗證volatile指令重排-理論篇

        Java併發編程之驗證volatile指令重排-理論篇java

        Java併發包下的類中大量使用了volatile關鍵字。經過以前文章介紹,你們已經知道了volatile的三大特性:共享變量可見性;不保證原子性;禁止指令重排後順序性。經過前面兩篇文章咱們經過代碼驗證了前兩個特性,本文咱們就來驗證禁止指令重排保證順序性。程序員

        指令重排序的生活例子

        去餐廳吃飯預約位置的的時候。假設要去A餐廳吃飯,A餐廳有前臺B、服務員C以及老闆D。若是就只有你一我的去吃飯的時候,你給前臺或者給服務器或者給老闆說一聲把2號桌預約了,半小時後過來。餐廳在爲了2小時內就你一我的去吃飯。那麼OK,沒問題,別說等半個小時,就是等一個小時,2號桌仍是你的。編程

        可是,若是如今是吃飯高峯期,不少人來吃飯,你給前臺說了,前臺忙着沒有及時給服務員或者沒有給老闆說,這個時候有個路人甲來吃飯,恰好看到2號桌沒人,老闆或者服務員就讓他就坐2號桌吃飯了。那麼,等你過來的時候,2號桌已經有人了。這個時候對於你來講,這個結果就不是你想要的了。緩存

        上面案例,若是從計算機執行指令角度來分析的話,你要到2號桌吃飯,這是預期結果。餐廳A就至關因而處理器,前臺B就至關因而編譯器,服務員C和老闆D就是指令和內存系統。若是你預約的時間點不是吃飯高峯期或者沒有人去餐廳A吃飯。那麼你就至關因而一個線程。就是單線程的。老闆、前臺、服務員怎麼安排均可以。由於只有你一個2號桌確定是你的。這是單線程狀況下。預期結果與實際結果就是一致的。安全

        若是你預約的時間點是吃飯高峯期,不少人來吃飯(不少線程),這個時候爲了餐廳效益,不管是前臺仍是服務員或者是老闆都會對你的位置進行重排序。在你沒有來的時候,會安排其餘人到你預約的位置吃飯。若是其餘人在你的位置吃飯,這個時候你再來吃飯,那麼實際結果和預期結果就不同了。這個時候餐廳應該作出相應的賠償。爲了解決這種賠償問題,老闆就想到了一個方案。作個牌子放在客人預約的桌子上。服務器

        當前臺或者是服務員或者是老闆看到餐桌上放的這個牌子,就知道這個位置不能再調動了。其中這個放在餐桌上的牌子就是特殊類型的內存屏障了。多線程

        示意圖以下:併發

        0fJheB4W1TM

        

        再來舉個更常見的例子:ide

        考試,在考試的時候老師會告訴咱們,先作會作的,不會作的放到後面作。假設出題老師出題順序是1-5,可是考試會根據本身實際狀況作題順序有多是一、二、四、五、3或者是一、三、四、五、2等等。若是把出題老師看着是寫代碼的程序員,題目的順序是代碼一行一行的順序,你的老師會告訴你先作會作的,此時老師就至關因而編譯器,會排序一次。而後你本身作的時候又會進行從新排序,你本身就至關因而處理器又排序了一次。性能

        上面兩個現實生活中的案例,咱們弄明白後,再來看看在計算機中指令重排問題,就很容易理解了。

        指令重排

        咱們程序員編寫的代碼在JVM執行的時候,爲了提升性能,編譯器和處理器都會對代碼編譯後的指令進行重排序。分爲3種:

        1:編譯器優化重排:

        編譯器的優化前提是在保證不改變單線程語義的狀況下,對從新安排語句的執行順序。

        2:指令並行重排:

        若是代碼中某些語句之間不存在數據依賴,處理器能夠改變語句對應機器指令的順序

        如:int x = 10;int y = 5;對於這種x y之間沒有數據依賴關係的,機器指令就會進行從新排序。可是對於:int x = 10; int y = 5; int z = x+y;這種的,由於z和x y之間存在數據依賴(z=x+y)關係。在這種狀況下,機器指令就不會把z排序在xy前面。

        3:內存系統的重排序

        經過以前的學習,咱們知道了處理器和主內存之間還存在一二三級緩存。這些讀寫緩存的存在,使得程序的加載和存取操做,多是亂序無章的。

        指令重排序的流程圖

        經過上面介紹,咱們能夠知道從程序員寫的Java源碼處處理器真正實際執行的指令序列,會經歷以下圖的過程:

        0fJheBRpS2S

        

        執行順序:

        源碼編譯器優化重排序(第一次排序) 指令重排序(第二次)內存重排序(第三次) 最終指向的指令。

        不管是第一次編譯器的重排序仍是第2、三次的處理器重排序。這些重排序當在多線程的場景下可能會出現線程可見性的問題。

        如在多線程的狀況下,單例模式就不安全了。

        爲了解決這個問題,JMM容許編譯器在生成指令順序的時候,能夠插入特定類型的內存屏障來禁止指令重排序。

        當一個變量使用volatile修飾的時候,volatile關鍵字就是內存屏障。當編譯器在生成指令順序的時候,發現了volatile,就直接忽略掉。再也不重排序了。

        示意圖:

        0fJheC69UkC

        

        證實volatile禁止指令重排演示代碼,歡迎繼續學習下一篇文章

        歡迎關注凱哥公衆號:凱哥Java(kaigejava)

e8369572a8d3666d7c0437b83367a212.jpg

相關文章
相關標籤/搜索