深入理解Java多執行緒與併發框(第③篇)——Java記憶體模型與原子性、可見性、有序性

無敵天驕發表於2020-03-23

一、Java記憶體模型

Java Memory Modle,簡稱 JMM,中文名稱 Java記憶體模型,它是一個抽象的概念,用來描述或者規範訪問記憶體變數的方式。因為各中計算機的作業系統和硬體不同,方式機制也可能不同,Java記憶體模型用於遮蔽(適配)各種差異,以此來達到訪問各個平臺的一致的效果。這也是Java誇平臺的重要原因之一。

主記憶體: Java記憶體規定了所有變數都儲存在主記憶體(Main Memory)中,各個執行緒又有自己的本地記憶體(工作記憶體),本地記憶體儲存著主記憶體中部分變數。具體訪問方式如下:

JMM工作方式

  • lock加鎖:為了保證訪問主記憶體變數的執行緒安全性,在訪問前一般會加鎖處理;
  • read讀:從主記憶體中讀取一個變數到工作記憶體;
  • load載入:把read讀到的變數載入到工作記憶體的變數副本中;
  • use使用:此時執行緒可以使用其工作記憶體中的變數了;
  • assign賦值:將處理後的變數賦值給工作記憶體中的變數;
  • store儲存:將工作記憶體中的變數儲存到主記憶體中,以新建new 一個新變數的方式儲存;
  • write寫:將store存在的新變數的引用賦值給被處理的變數;
  • unload解鎖:所有的工作做完,最後解鎖釋放資源。

二、Java記憶體模型的三大特性

1. 原子性(Atomicity)

這裡的原子性如同資料庫事務中是原子性,一個或多個操作要麼全執行成功要麼全執行失敗(全不執行)。

int a = 1;
a++;
double b = 1.5;

Java記憶體模型只保證單一的操作具有原子性,比如上面的 int a = 1; 是一個單子的操作,所以具有原子性。而 a++ 操作在底層會分為三個操作:1)、讀取a的值給臨時變數;2)、臨時變數a的值加1操作;3)、將加操作後的值賦值給a。每個操作都是原子的,但Java記憶體模型在多執行緒下並不能保證多操作具有整體原子性,因為它也不知道這個整體內有多少操作,使用者想要達到多操作具有整體原子性,需要對響應的程式碼塊做同步(synchronous)處理,比如使用 有鎖的synchronized 或 無鎖的CAS。

2. 可見性(Visibility)

這裡的可見性是記憶體可見性。

如上圖,執行緒1和執行緒2在未同步的情況下對共享記憶體(主記憶體)中的變數進行訪問,比如兩個執行緒的操作都是對變數a進行加1操作。假設執行緒1首先獲取主記憶體中變數a的值,隨後執行緒2又獲取了主記憶體變數a的值,此時它們工作記憶體中a的值都是1,它們各自將a的值加1操作,然後assign至工作記憶體,工作記憶體中變數a的值都是2,然後兩個執行緒又將值重新整理到主記憶體,最後的結果是主記憶體中變數a的值是2。雖然整體對a的值加1操作做了兩次操作,但由於執行緒間的操作是互相隔離的,預設情況下無法感知記憶體變數的值在隨後的變化,也就無法訪問記憶體中最新的變數值,這就是記憶體可行性的問題。

如何解決記憶體可見性的問題?

  • 對進入臨界區的執行緒做同步處理(比如 synchronized),同一時刻僅有一個執行緒能夠訪問臨界區的資源;
  • 使用 volatile 關鍵字保證記憶體可見性,它能保證訪問臨界區資源的所有執行緒總能看到共享資源的最新值;
  • CAS無鎖化。

3. 有序性(Ordering)

執行緒內的所有操作都是有序的,既程式執行的順序按照程式碼的先後順序執行。比如下面的示例:

int a = 1;
int b = 2;
int c = a + b;

執行緒內程式會先執行 int a = 1; ,然後執行 int b = 2; 最後執行int c = a + b;。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2682073/,如需轉載,請註明出處,否則將追究法律責任。

相關文章