Java記憶體模型的歷史變遷

程曉明發表於2015-05-21

本文通過介紹Java的新/舊記憶體模型,來展示Java技術的歷史變遷。

舊的Java記憶體模型

Java使用的是共享記憶體的併發模型,線上程之間共享變數。Java語言定義了執行緒模型規範,通過記憶體模型控制執行緒與變數的互動,從而實現Java執行緒之間的通訊。在JDK5之前,Java一直使用的是舊記憶體模型。如圖1所示。變數儲存在由所有執行緒共享的主記憶體中,主記憶體中的變數稱為mastingcopy。每個執行緒都有一個工作記憶體,它儲存變數的workingcopy。舊的記憶體模型定義了若干規則,通過這些規則來保證執行緒何時將主記憶體中的mastingcopy傳送到執行緒的工作記憶體中;以及執行緒何時將工作記憶體中的workingcopy傳送回主記憶體。舊記憶體模型使用8個操作來定義執行緒可以執行的動作。

  • read(讀)操作:主記憶體把mastingcopy傳送到執行緒的工作記憶體,以供後面的load操作使用。
  • load(裝載)操作:執行緒將由read操作從主記憶體傳送過來的值,放入工作記憶體中。
  • use(使用)操作:執行緒將變數的workingcopy傳送到執行緒執行引擎。
  • assign(賦值)操作:執行緒將變數值從執行緒執行引擎傳送到執行緒的工作記憶體中。
  • store(儲存)操作:執行緒將變數的workingcopy傳送到主記憶體,供後面的write操作使用。
  • write(寫)操作:主記憶體將由store操作傳送過來的值,放入主記憶體中。
  • lock(鎖定)操作:執行緒獲得指定物件的鎖。
  • unlock(解鎖)操作:執行緒釋放指定物件的鎖。

圖1 Java舊記憶體模型

這裡的關鍵是,由於read操作是由主記憶體執行,而對應的load是由執行緒執行,read操作和load操作之間是鬆散耦合的。也就是說,主記憶體和執行緒工作記憶體之間的變數傳遞是鬆散耦合的。同樣,由於store操作是由執行緒執行,而對應的write是由主記憶體執行,store操作和write操作之間是鬆散耦合的。也就是說,執行緒工作記憶體和主記憶體之間的變數傳遞是鬆散耦合的。舊Java記憶體模型對Java實現如何執行變數的讀/寫,加鎖/解鎖,以及Volatile變數的讀/寫,定義了非常嚴格的規則。這些規則非常複雜,具體詳情請參考《JVM規範》,這裡就不贅述了。舊Java記憶體模型通過這些複雜的規則,來保證多執行緒程式的執行緒之間,可以可靠地傳遞共享變數,從而保證多執行緒程式的正確性。

新的Java記憶體模型

從JDK5開始,Java使用新的記憶體模型,新記憶體模型完全拋棄了舊記憶體模型的主記憶體和工作記憶體的概念,也拋棄了舊記憶體模型的8個記憶體操作。也就是說,新記憶體模型完全是重新設計的。

新記憶體模型引入了一個新的概念,叫happens-before。happens-before的概念最初由LeslieLamport在其一篇影響深遠的論文(《Time,ClocksandtheOrderingofEventsinaDistributedSystem》)中提出。LeslieLamport使用happens-before來定義分散式系統中,事件之間的一個偏序關係(partialordering)。LeslieLamport在這篇論文中給出了一個分散式演算法,該演算法可以將該偏序關係擴充套件為某種全序關係。

JSR-133使用happens-before的概念來指定兩個操作(這裡的操作是指程式中對變數的讀/寫,對鎖的加鎖和解鎖)之間的執行順序。新記憶體模型定義瞭如下的happens-before規則。

  • 程式順序規則:一個執行緒中的每個操作,happensbefore於該執行緒中的任意後續操作。
  • 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
  • volatile變數規則:對一個volatile域的寫,happensbefore於任意後續對這個volatile域的讀。
  • 傳遞性:如果Ahappens-beforeB,且BhappensbeforeC,那麼Ahappens-beforeC。
  • start()規則:如果執行緒A執行操作ThreadB.start()(啟動執行緒B),那麼A執行緒的ThreadB.start()操作happensbefore於執行緒B中的任意操作。
  • join()規則:如果執行緒A執行操作ThreadB.join()併成功返回,那麼執行緒B中的任意操作happens-before於執行緒A從ThreadB.join()操作成功返回。

由於兩個操作可以在一個執行緒之內,也可以是在不同執行緒之間。因此JMM可以通過happens-before關係向程式設計師提供跨執行緒的記憶體可見性保證(如果A執行緒的寫操作a與B執行緒的讀操作b之間存在happens-before關係,儘管a操作和b操作在不同的執行緒中執行,但JMM

圖2新記憶體模型的設計示意圖

向程式設計師保證a操作將對b操作可見)。在新記憶體模型向程式設計師提供happens-before規則,程式設計師只需要與happens-before打交道即可,因此Java程式設計師的學習負擔大大降低。同時,新記憶體模型允許不會改變程式結果的重排序,這可以最大限度地放鬆對編譯器和處理器的束縛,新記憶體模型的執行效能比舊記憶體模型要好。

相關文章