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


一、硬件內存架構

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

當一個CPU需要讀取主存時,它會將主存的部分讀到CPU緩存中。它甚至可能將緩存中的部分內容讀到它的內部寄存器中,然后在寄存器中執行操作。

當CPU需要將結果寫回到主存中去時,它會將內部寄存器的值刷新到緩存中,然后在某個時間點將值刷新回主存。

二、並發編程的問題

並發編程,為了保證數據的安全,需要滿足以下三個特性:

原子性:在一個操作中就是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包等實現原子性、有序性、可見性。

三、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變量當作一個普通變量來對待。

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

四、順序一致性內存模型

順序一致性內存模型是一個被計算機科學家理想化了的理論參考模型,它為程序員提供了極強的內存可見性保證(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()方法

五、處理器內存模型

如果完全按照順序一致性模型來實現,那么很多的處理器和編譯器優化都要被禁止,這對執行性能將會有很大的影響。

根據對不同類型讀/寫操作組合的執行順序的放松,可以把常見處理器的內存模型划分為下面幾種類型:

  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內存模型

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM