併發系列(二)----Java內存模型

一 簡介

    在併發編程中,兩個線程(A、B)同時操做一個普通變量的時候會出現線程A在操做變量時線程B也將變量操做了,此時線程A是沒法感知變量發生變化的,形成變量改變錯誤。更據以上例子咱們須要解決的問題就是線程之間的通訊以及同步。表在命令式編程中,線程之間的通訊機制有兩種:共享內存和消息傳遞。Java併發採用的是共享內存模型,Java線程之間的通訊老是隱式進行,整個通訊對程序員玩徹底透明。java

二 Java內存模型的抽象結構

    Java線程之間的通訊由Java內存模型(本文簡稱爲JMM)控制,JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每一個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其餘的硬件和編譯器優化。Java內存模型的抽象示意圖以下。程序員

 


 

從上圖來看,線程A與線程B之間如要通訊的話,必需要經歷下面2個步驟:編程

    1.首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去。緩存

    2.而後,線程B到主內存中去讀取線程A以前已更新過的共享變量。多線程

下面經過示意圖來講明這兩個步驟:併發

 


 

如上圖所示,本地內存A和B有主內存中共享變量x的副本。假設初始時,這三個內存中的x值都爲0。線程A在執行時,把更新後的x值(假設值爲1)臨時存放在本身的本地內存A中。當線程A和線程B須要通訊時,線程A首先會把本身本地內存中修改後的x值刷新到主內存中,此時主內存中的x值變爲了1。隨後,線程B到主內存中去讀取線程A更新後的x值,此時線程B的本地內存的x值也變爲了1。app

從總體來看,這兩個步驟實質上是線程A在向線程B發送消息,並且這個通訊過程必需要通過主內存。JMM經過控制主內存與每一個線程的本地內存之間的交互,來爲java程序員提供內存可見性保證。性能

三 重排序

在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序。重排序分3種類型。學習

1. 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。優化

2. 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-LevelParallelism,ILP)來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。

3. 內存系統的重排序。因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行

從java源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:


 

上述的1屬於編譯器重排序,2和3屬於處理器重排序。這些重排序可能會致使多線程程序出現內存可見性問題。對於編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是全部的編譯器重排序都要禁止)。對於處理器重排序,JMM的處理器重排序規則會要求Java編譯器在生成指令序列時,插入特定類型的內存屏障(Memory Barriers,Intel稱之爲Memory Fence)指令,經過內存屏障指令來禁止特定類型的處理器重排序。JMM屬於語言級的內存模型,它確保在不一樣的編譯器和不一樣的處理器平臺之上,經過禁止特定類型的編譯器重排序和處理器重排序,爲程序員提供一致的內存可見性保證。

爲了保證內存可見性,Java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。JMM把內存屏障指令分爲4類

 


 

四 happens-before

JSR-133提出了happens-before的概念,經過這個概念來闡述操做之間的內存可見性。若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必須存在happens-before關係。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。 與程序員密切相關的happens-before規則以下:

1.程序順序規則:一個線程中的每一個操做,happens-before於該線程中的任意後續操做。

2. 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。

3. volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀

4. 傳遞性:若是A happens-before B,且B happens-before C,那麼A happens-before C。

注意:兩個操做之間具備happens-before關係,並不意味着前一個操做必需要在後一個操做以前執行!happens-before僅僅要求前一個操做(執行的結果)對後一個操做可見,且前一個操做按順序排在第二個操做以前

5.start()規則:若是線程A執行操做ThreadB.start()(啓動線程B),那麼A線程ThreadB.start()操做happens-before於線程B中的任意操做。

6.join()規則:若是線程A執行操做ThreadB.join()併成功返回,那麼線程B中的任意操happens-before於線程A從ThreadB.join()操做成功返回

happens-before與JMM的關係以下圖所示:

 


 

如上圖所示,一個happens-before規則一般對應於多個編譯器重排序規則和處理器重排序規則。對於java程序員來講,happens-before規則簡單易懂,它避免程序員爲了理解JMM提供的內存可見性保證而去學習複雜的重排序規則以及這些規則的具體實現

五 說明

只要是內存模型的就逃不掉程曉明寫的內存深刻理解Java內存模型,我是看完他的《深刻Java內存模型》寫的原本想作個總結的結果總結下來發現90%仍是書上的。本人能力有限將我認爲比較總要的有助於理解Java的內存模型的東西寫到這裏面。參考文獻裏面有程曉敏博客地址

深刻理解Java內存模型(一)——基礎

相關文章
相關標籤/搜索