Java併發(二):Java內存模型

1、硬件內存架構

一個現代計算機一般由兩個或者多個CPU。其中一些CPU還有多核。每一個CPU在某一時刻運行一個線程是沒有問題的。若是你的Java程序是多線程的,在你的Java程序中每一個CPU上一個線程可能同時(併發)執行。html

當一個CPU須要讀取主存時,它會將主存的部分讀到CPU緩存中。它甚至可能將緩存中的部份內容讀到它的內部寄存器中,而後在寄存器中執行操做。java

當CPU須要將結果寫回到主存中去時,它會將內部寄存器的值刷新到緩存中,而後在某個時間點將值刷新回主存。程序員

2、併發編程的問題

併發編程,爲了保證數據的安全,須要知足如下三個特性:編程

原子性:在一個操做中就是cpu不能夠在中途暫停而後再調度,既不被中斷操做,要不執行完成,要不就不執行。(處理器優化)數組

  原子性問題:線程在執行一個讀改寫操做時,在執行讀改以後,時間片耗完,就會被要求放棄CPU,並等待從新調度。此時另外一個線程對同一個變量執行讀改寫操做就會出現問題。這種狀況下,讀改寫就不是一個原子操做。緩存

i = 0;      // 基本數據類型的變量和賦值操做都是原子性操做
j = i ;     // 包含了兩個操做:讀取i,將i值賦值給j 
i++;         // 包含了三個操做:讀取i值、i + 1 、將+1結果賦值給i
i = j + 1;  // 包含了三個操做:讀取j值、j + 1 、將+1結果賦值給i

  在單線程環境下咱們能夠認爲整個步驟都是原子性操做。可是在多線程環境下則不一樣,Java只保證了基本數據類型的變量和賦值操做纔是原子性的。要想在多線程環境下保證原子性,則能夠經過鎖、synchronized來確保。安全

可見性:當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。(緩存一致性問題)多線程

有序性:程序執行的順序按照代碼的前後順序執行。(指令重排)架構

內存模型經過限制處理器優化和使用內存屏障,來保證共享內存的正確性(可見性、有序性、原子性)。併發

Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java程序在各類平臺下對內存的訪問都能保證效果一致的機制及規範。

JMM還經過volatilesynchronizedfinalconcurren包等實現原子性、有序性、可見性。

3、Java內存模型(JMM)

共享變量:堆內存在線程之間共享,存儲在堆內存中全部實例域、靜態域和數組元素共享變量

  (局部變量,方法定義參數、異常處理器參數不會在線程之間共享,不會有內存可見性問題,不受內存模型的影響)

JMM定義了線程和主內存之間的抽象關係:

  1)線程之間的共享變量存儲在主內存(main memory)中

  2)每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程用以讀/寫共享變量的副本

  3)本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化

線程A與線程B通訊:

  1)線程A把本地內存A中更新過的共享變量刷新到主內存中去

  2)線程B到主內存中去讀取線程A以前已更新過的共享變量

JMM經過控制主內存與每一個線程的本地內存之間的交互,提供內存可見性保證

JMM的設計

1)常見的處理器內存模型比JMM要弱,java編譯器在生成字節碼時,會在執行指令序列的適當位置插入內存屏障來限制處理器的重排序。

2)因爲各類處理器內存模型的強弱並不相同,爲了在不一樣的處理器平臺向程序員展現一個一致的內存模型,JMM在不一樣的處理器中須要插入的內存屏障的數量和種類也不相同。

程序員但願:強內存模型編程,易於理解,易於編程

編譯器和處理器但願:弱內存模型,內存模型對它們的束縛越少越好,以提升性能

JMM時的核心目標就是找到一個好的平衡點:一方面要爲程序員提供足夠強的內存可見性保證;另外一方面,對編譯器和處理器的限制要儘量的放鬆。

JMM把happens- before要求禁止的重排序分爲了下面兩類:

1)會改變程序執行結果的重排序,JMM要求編譯器和處理器必須禁止這種重排序。

2)不會改變程序執行結果的重排序,JMM對編譯器和處理器不做要求(JMM容許這種重排序)。

  只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。

  好比,若是編譯器通過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖能夠被消除。

  再好比,若是編譯器通過細緻的分析後,認定一個volatile變量僅僅只會被單個線程訪問,那麼編譯器能夠把這個volatile變量看成一個普通變量來對待。

  這些優化既不會改變程序的執行結果,又能提升程序的執行效率。

4、順序一致性內存模型

順序一致性內存模型是一個被計算機科學家理想化了的理論參考模型,它爲程序員提供了極強的內存可見性保證(JMM沒有順序一致性內存模型保證)

特性:

  • 一個線程中的全部操做必須按照程序的順序來執行。
  • (無論程序是否同步)全部線程都只能看到一個單一的操做執行順序。在順序一致性內存模型中,每一個操做都必須原子執行且馬上對全部線程可見。

視圖信息:

1.順序一致性模型有一個單一的全局內存

2.在任意時間點最多隻能有一個線程能夠鏈接到內存

3.每個線程必須按程序的順序來執行內存讀/寫操做

舉例:

線程A:A1->A2->A3  線程B:B1->B2->B3  併發執行

正確同步:

兩個線程沒有作同步:

能夠看出:

1.每一個線程內部執行順序 都是按照程序的順序來執行

2.全部線程都只能看到一個一致的總體執行順序(緣由:順序一致性內存模型中的每一個操做必須當即對任意線程可見)

順序一致性模型與JMM區別:

  順序一致性模型保證單線程內的操做會按程序的順序執行,JMM不保證單線程內的操做會按程序的順序執行(遵照as-if-serial語義)

  順序一致性模型保證全部線程只能看到一致的操做執行順序,而JMM不保證全部線程能看到一致的操做執行順序

JMM在具體實現上的基本方針:在不改變(正確同步的)程序執行結果的前提下,儘量的爲編譯器和處理器的優化打開方便之門。 

正確同步,JMM保證程序的執行結果將與該程序在順序一致性模型中的執行結果相同(但不保證執行順序)

 

假設A線程執行writer()方法後,B線程執行reader()方法

5、處理器內存模型

若是徹底按照順序一致性模型來實現,那麼不少的處理器和編譯器優化都要被禁止,這對執行性能將會有很大的影響。

根據對不一樣類型讀/寫操做組合的執行順序的放鬆,能夠把常見處理器的內存模型劃分爲下面幾種類型:

  1. 放鬆程序中寫-讀操做的順序,由此產生了total store ordering內存模型(簡稱爲TSO)。
  2. 在前面1的基礎上,繼續放鬆程序中寫-寫操做的順序,由此產生了partial store order 內存模型(簡稱爲PSO)。
  3. 在前面1和2的基礎上,繼續放鬆程序中讀-寫和讀-讀操做的順序,由此產生了relaxed memory order內存模型(簡稱爲RMO)和PowerPC內存模型。

注意,這裏處理器對讀/寫操做的放鬆,是以兩個操做之間不存在數據依賴性爲前提的(由於處理器要遵照as-if-serial語義,處理器不會對存在數據依賴性的兩個內存操做作重排序)。

從上到下,模型由強變弱。越是追求性能的處理器,內存模型設計的會越弱。由於這些處理器但願內存模型對它們的束縛越少越好,這樣它們就能夠作儘量多的優化來提升性能。

JMM,處理器內存模型,順序一致性內存模型之間的關係

JMM是一個語言級的內存模型,處理器內存模型是硬件級的內存模型,順序一致性內存模型是一個理論參考模型。

語言內存模型,處理器內存模型和順序一致性內存模型的強弱對比示意圖:

內存模型越強,越容易保證內存可見性,易編程性就越好。可是重排序就會越少,執行效率就越低。

 

重排序 :Java併發(三):重排序

happens-before:Java併發(四):happens-before

volatile:Java併發(六):volatile的實現原理

Final:Java併發(十九):final實現原理

 

 

 參考資料:

《成神之路-基礎篇》JVM——Java內存模型 

細說Java多線程以內存可見性

Java內存模型FAQ

深刻理解Java內存模型

相關文章
相關標籤/搜索