1. 程式人生 > >java內存模型詳解

java內存模型詳解

讀寫 tro center 設計者 === 就是 單線程 名稱 1-1

對於本篇文章,將從四個概念來介紹:內存模型基礎,重排序,順序一致性和happens-before

1.內存模型基礎

在並發編程中,有兩個關鍵問題:線程之間如何通信和如何同步。由此而引出了兩種並發模型:共享內存的並發模型和消息傳遞的並發模型。

1.1 消息傳遞的並發模型

該模型是指兩個線程之間通過發送消息來進行顯式的通信,而同步則是隱式進行的,因為發送消息的動作要先於接收消息。go語言采用的就是這種並發模型。

1.2 共享內存的並發模型

該模型是兩個線程之間通過共享內存中的公共狀態,然後讀寫公共狀態來進行隱式通信的,而同步則需要顯式的進行控制。java語言采用的就是共享內存的並發模型。

java線程之間的通信是由java內存模型(JMM)控制,它定義了主內存和線程之間的抽象關系:線程之間的共享變量存儲在主內存中,每個線程都有一個私有的本地內存,本地內存存儲了該線程讀/寫共享變量的副本,如下圖:

技術分享圖片

如圖,如果線程A和線程B想要通信,需要經過以下步驟:

  • 線程A把本地內存中更新過的共享變量的副本刷新到主內存中
  • 線程B到主內存中讀取線程A已經更新過的共享變量

如此一來,線程A和線程B就通過主內存進行間接的通信了。這就是java內存模型的概念。

2.重排序

2.1 重排序概念

重排序是指編譯器和處理器為了優化程序性能而對指令序列進行重新排序的一種手段。通過下面一個例子來說明:

處理器A 處理器B
代碼

a = 1; //A1

x = b; //A2

b = 2; //B1

y = a; //B2

運行結果

初始狀態:a = b = 0

處理器執行後可能得到的結果:x = y = 0

假設處理器A和處理器B按程序的順序並行執行代碼,可能得到的結果是x = y = 0;為什麽呢?這就涉及到了重排序。我們知道處理器使用了寫緩沖區臨時保存了向內存中寫入的數據,比如在步驟A1中,準確的說是分為了兩個步驟 A1-1:把a = 1 保存到寫緩沖區中,A1-2:把 a = 1 從緩沖區刷新到主內存中,只有這兩個步驟都完成了,才可以說A1步驟完成了。考慮一下這種情況:A1-1 ===》A2 ===》A1-2,也就是說內存實際發生順序變為了 A2 ===》A1,如果處理器B也是如此,此時得到的結果就是x = y = 0;因為處理器執行內存操作的順序和內存操作實際發生的順序不一致,這就是重排序。

2.2 重排序遵循的原則

2.2.1 數據依賴性

數據依賴性定義:如果同一個處理器和同一個線程的兩個操作訪問同一個變量,並且這兩個操作中有一個是寫操作,此時這兩個操作之間就存在數據依賴性。數據依賴性有三種類型:

名稱 代碼示例 說明
寫後讀

a = 1;

b = a;

寫一個變量之後,再讀這個變量
寫後寫

a = 1;

b = 2;

寫這個變量之後,再寫這個變量
讀後寫

a = b;

b = 1;

讀一個變量之後,再寫這個變量

編譯器和處理器可能會對操作做重排序,但是會遵守數據依賴性,不會改變存在數據依賴關系的兩個操作的執行順序。這裏的數據依賴性是指單個處理器和單個線程中執行的操作。

2.2.2 遵守as-if-serial語義

as-if-serial的語義是:無論怎麽重排序,單線程程序的執行結果是不能改變的。為了遵守as-if-serial語義,編譯器和處理器不會對存在數據依賴關系的操作做重排序。

3. 順序一致性內存模型

順序一致性內存模型,是一個理想化的理論參考模型,它提供了極強的內存可見性。它有兩大特征:

  • 一個線程中所有的操作必須按照程序的順序來執行
  • 不管程序是否同步,所有線程都只能看到一個單一的操作執行順序,在順序一致性模型中,每個操作必須是原子執行且立刻對所有線程可見。

4.happens-before

happens-before是JMM的核心概念,JMM的設計者,在設計JMM時考慮兩個因素:從程序員的角度,使用JMM時希望是一個強內存模型,易於使用;從編譯器和處理器的角度,希望JMM是一個弱內存模型,對它的束縛越少越好,這樣它能最大程度的優化來提高性能。

最終,JMM遵循了一個原則:在不改變程序執行結果(單線程程序和正確同步的多線程程序)的前提下,處理器和編譯器怎麽優化都可以。

4.1 happens-before的定義

  • 如果一個操作happens-before另外一個操作,那麽第一個操作的執行結果對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
  • 兩個操作存在happens-before關系,並不意味著java平臺的具體實現必須要按照happens-before關系指定的順序來執行。如果重排序之後的執行結果和按照happens-before關系來執行的結果一致,那麽JMM允許這種重排序。

4.2 happens-before規則

  • 程序順序規則:一個線程中的每個操作,happens-before於該線程中的任意後續操作。
  • 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
  • volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
  • 傳遞性:如果A happens-before B,B happens-before C,那麽 A happens-before C
  • start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那麽A線程的ThreadB.start()操作happens-before於線程B中的任意操作。
  • join()規則:如果線程A執行操作ThreadB.join()並成功返回,那麽線程B的任意操作Happens-before於線程A從ThreadB.join()操作成功返回。

參考《java並發編程的藝術》

java內存模型詳解