Java記憶體模型[轉]

來源:互聯網
上載者:User

標籤:成熟   record   3.2   class   資料   改變   響應   img   單線程   

原文:http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

1. 概述

  多任務和高並發是衡量一台電腦處理器的能力重要指標之一。一般衡量一個伺服器效能的高低好壞,使用每秒交易處理數(Transactions Per Second,TPS)這個指標比較能說明問題,它代表著一秒內伺服器平均能響應的請求數,而TPS值與程式的並發能力有著非常密切的關係。在討論Java記憶體模型和線程之前,先簡單介紹一下硬體的效率與一致性。

2.硬體的效率與一致性

  由於電腦的存放裝置與處理器的運算能力之間有幾個數量級的差距,所以現代電腦系統都不得不加入一層讀寫速度儘可能接近處理器運算速度的快取(cache)來作為記憶體與處理器之間的緩衝:將運算需要使用到的資料複製到緩衝中,讓運算能快速進行,當運算結束後再從緩衝同步回記憶體之中沒這樣處理器就無需等待緩慢的記憶體讀寫了。
  基於快取的儲存互動很好地解決了處理器與記憶體的速度矛盾,但是引入了一個新的問題:緩衝一致性(Cache Coherence)。在多處理器系統中,每個處理器都有自己的快取,而他們又共用同一主存,如所示:多個處理器運算任務都涉及同一塊主存,需要一種協議可以保障資料的一致性,這類協議有MSI、MESI、MOSI及Dragon Protocol等。Java虛擬機器記憶體模型中定義的記憶體訪問操作與硬體的緩衝訪問操作是具有可比性的,後續將介紹Java記憶體模型。

  除此之外,為了使得處理器內部的運算單元能竟可能被充分利用,處理器可能會對輸入代碼進行亂起執行(Out-Of-Order Execution)最佳化,處理器會在計算之後將對亂序執行的代碼進行結果重組,保證結果準確性。與處理器的亂序執行最佳化類似,Java虛擬機器的即時編譯器中也有類似的指令重排序(Instruction Recorder)最佳化。

3.Java記憶體模型

  定義Java記憶體模型並不是一件容易的事情,這個模型必須定義得足夠嚴謹,才能讓Java的並行作業不會產生歧義;但是,也必須得足夠寬鬆,使得虛擬機器的實現能有足夠的自由空間去利用硬體的各種特性(寄存器、快取等)來擷取更好的執行速度。經過長時間的驗證和修補,在JDK1.5發布後,Java記憶體模型就已經成熟和完善起來了。

3.1 主記憶體與工作記憶體

  Java記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣底層細節。此處的變數與Java編程時所說的變數不一樣,指包括了執行個體欄位、靜態欄位和構成數組對象的元素,但是不包括局部變數與方法參數,後者是線程私人的,不會被共用。

  Java記憶體模型中規定了所有的變數都儲存在主記憶體中,每條線程還有自己的工作記憶體(可以與前面將的處理器的快取類比),線程的工作記憶體中儲存了該線程使用到的變數到主記憶體副本拷貝,線程對變數的所有操作(讀取、賦值)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數。不同線程之間無法直接存取對方工作記憶體中的變數,線程間變數值的傳遞均需要在主記憶體來完成,線程、主記憶體和工作記憶體的互動關係如所示,和很類似。

這裡的主記憶體、工作記憶體與Java記憶體地區的Java堆、棧、方法區不是同一層次記憶體劃分。

3.2 記憶體間互動操作

  關於主記憶體與工作記憶體之間的具體互動協議,即一個變數如何從主記憶體拷貝到工作記憶體、如何從工作記憶體同步到主記憶體之間的實現細節,Java記憶體模型定義了以下八種操作來完成:

  • lock(鎖定):作用於主記憶體的變數,把一個變數標識為一條線程獨佔狀態。
  • unlock(解鎖):作用於主記憶體變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他線程鎖定。
  • read(讀取):作用於主記憶體變數,把一個變數值從主記憶體傳輸到線程的工作記憶體中,以便隨後的load動作使用
  • load(載入):作用於工作記憶體的變數,它把read操作從主記憶體中得到的變數值放入工作記憶體的變數副本中。
  • use(使用):作用於工作記憶體的變數,把工作記憶體中的一個變數值傳遞給執行引擎,每當虛擬機器遇到一個需要使用變數的值的位元組碼指令時將會執行這個操作。
  • assign(賦值):作用於工作記憶體的變數,它把一個從執行引擎接收到的值賦值給工作記憶體的變數,每當虛擬機器遇到一個給變數賦值的位元組碼指令時執行這個操作。
  • store(儲存):作用於工作記憶體的變數,把工作記憶體中的一個變數的值傳送到主記憶體中,以便隨後的write的操作。
  • write(寫入):作用於主記憶體的變數,它把store操作從工作記憶體中一個變數的值傳送到主記憶體的變數中。

  如果要把一個變數從主記憶體中複製到工作記憶體,就需要按順尋地執行read和load操作,如果把變數從工作記憶體中同步回主記憶體中,就要按順序地執行store和write操作。Java記憶體模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是可以插入其他指令的,如對主記憶體中的變數a、b進行訪問時,可能的順序是read a,read b,load b, load a。Java記憶體模型還規定了在執行上述八種基本操作時,必須滿足如下規則:

  • 不允許read和load、store和write操作之一單獨出現
  • 不允許一個線程丟棄它的最近assign的操作,即變數在工作記憶體中改變了之後必須同步到主記憶體中。
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把資料從工作記憶體同步回主記憶體中。
  • 一個新的變數只能在主記憶體中誕生,不允許在工作記憶體中直接使用一個未被初始化(load或assign)的變數。即就是對一個變數實施use和store操作之前,必須先執行過了assign和load操作。
  • 一個變數在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
  • 如果對一個變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前需要重新執行load或assign操作初始設定變數的值
  • 如果一個變數事先沒有被lock伺服器用戶端檔案鎖,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變數。
  • 對一個變數執行unlock操作之前,必須先把此變數同步到主記憶體中(執行store和write操作)。
 3.3 重排序

  在執行程式時為了提高效能,編譯器和處理器經常會對指令進行重排序。重排序分成三種類型:

  1. 編譯器最佳化的重排序。編譯器在不改變單線程程式語義放入前提下,可以重新安排語句的執行順序。
  2. 指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。
  3. 記憶體系統的重排序。由於處理器使用緩衝和讀寫緩衝區,這使得載入和儲存操作看上去可能是在亂序執行。

從Java原始碼到最終實際執行的指令序列,會經過下面三種重排序:

為了保證記憶體的可見度,Java編譯器在產生指令序列的適當位置會插入記憶體屏障指令來禁止特定類型的處理器重排序。Java記憶體模型把記憶體屏障分為LoadLoad、LoadStore、StoreLoad和StoreStore四種:

Java記憶體模型[轉]

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.