1. 程式人生 > >併發程式設計之Java記憶體模型

併發程式設計之Java記憶體模型

  在介紹Java記憶體模型之前,先來了解一下為什麼要有記憶體模型,以及記憶體模型是什麼。然後我們基於對記憶體模型的瞭解,學習Java記憶體模型以及併發程式設計的三大特性。

 

為什麼要有記憶體模型

  在計算機中,所有的運算操作都是由CPU的暫存器來完成的,CPU指令的執行需要涉及到資料的讀寫操作,而CPU只能訪問主存中的資料。隨著技術的發展,CPU的執行速度越來越快,而記憶體的訪問速度沒有太大的變化,導致CU每次操作主存都要等待很長的時間。於是就有了在CPU與主存之間新增快取的設計。

 

記憶體模型:CPU Cache模型

 

  目前快取的數量達到了3級,最接近CPU的快取稱為L1,然後為L2、L3和主存。由於程式指令和資料的行為和熱點分佈差異比較大,因此將L1又細分為L1i(istruction)、L1d(data)。

   CPU的出現是為了解決CPU直接訪問主存效率低下的問題。程式在執行過程中,會將運算所需的資料從主存中複製一份到Cache中,這樣CPU在計算時就可以直接對CPU Cache中的資料進行讀寫,當運算結束後,再將Cahce中的最新資料重新整理到主存中。     雖然快取的出現極大地提升了CPU的吞吐能力,但是也導致了快取不一致的問題。這是因為CPU都是對Cache中的資料進行讀寫,不同執行緒之間的工作記憶體是相互獨立的,對某個執行緒工作空間中的資料進行更新,可能會無法及時同步到其它快取中。     為了保證資料的正確性,記憶體模型定義了共享記憶體系統中多執行緒程式讀寫操作行為的規範。

 

Java記憶體模型

    Java記憶體模型(Java Memory Model ),簡稱JMM,是一種符合記憶體模型規範的,遮蔽了各種硬體和作業系統的訪問差異的,保證了Java程式在各種平臺下對記憶體的訪問都能得到一致效果的機制及規範。其目的是解決多執行緒通過主記憶體進行通訊時,存在的原子性、可見性(快取一致性)以及有序性問題。(關於原子性、可見性(快取一致性)以及有序性,我們將會在”併發程式設計的三大特性“中詳細講解)       JMM決定了一個執行緒對共享變數的寫入何時對其它執行緒可見,定義了執行緒與主存之間的關係:
  • 共享變數儲存於主存中,每個執行緒都可以訪問
  • 每個執行緒都有私有的工作記憶體,也稱為本地記憶體
  • 工作記憶體中只儲存共享變數的副本
  • 執行緒不能直接操作主存,只有操作了本地記憶體後才能寫入主存
  • 每一個執行緒都不能訪問其他執行緒的本地記憶體
 

 併發程式設計的三大特性

    併發程式設計有三大特性:原子性、可見性、有序性。
    •   原子性:是指在一次操作或多次操作中,要麼所有的操作都得到執行,要麼都不執行。【類似於事務】
      •   JMM只保證了基本讀取和賦值的原子性操作
      •   多個原子性操作的組合不再是原子性操作
      •   可以使用synchronized/lock保證某些程式碼片段的原子性
      •   對於int等型別的自增操作,可以通過java.util.concurrent.atomic.*保證原子性
    •   可見性:是指一個執行緒對共享變數進行了修改,其他執行緒可以立即看到修改後的值。
    •   有序性:是指程式碼在執行過程中的先後順序是有序的。【Java編譯器會對程式碼進行優化,執行順序可能與開發者編寫的順序不同(指令重排)】
    併發程式設計時,保證三大特性的方式有三種:
    •   使用volatile關鍵字修飾變數
      •   當一個變數被volatile關鍵字修飾時,對於共享變數的讀操作會直接在主存中進行,對於共享變數的寫操作是先修改本地記憶體,修改結束後直接刷到主存中。(未被volatile修飾的變數被修改後,什麼時候最新值會被刷到主存中是不確定的)
    •   使用synchronized關鍵字修飾方法或程式碼塊
      •   synchronized關鍵字能保證同一時刻只有一個執行緒獲得鎖然後執行同步方法,並且確保鎖釋放之前,會將修改的變數刷入主存。
    •   使用JUC提供的顯式鎖Lock
      •   Lock能保證同一時刻只有一個執行緒獲得鎖然後執行同步方法,並且確保鎖釋放之前,會將修改的變數刷入主存。

 補充

  Java中提供了一系列和併發處理相關的關鍵字,比如volatile、synchronized等,其實這些就是Java記憶體模型封裝了底層的實現後提供給程式設計師使用的一些關鍵字。

 

參考文獻

  • 汪文君《Java高併發程式設計詳解-多執行緒與架構設計》

&n