大家好,我是樹哥。
在前面一段時間,我連續寫了幾篇關於並行程式設計的文章:
這幾篇文章分別講了 Java 記憶體模型、happens-before 原則、volatile 關鍵字、synchronized 關鍵字、Java 物件的記憶體佈局。這 5 篇文章看著好像是獨立的,但實際上他們是互相關聯的。
在好幾年前我也看過 Java 記憶體模型這些內容,但網上的內容實在太多太雜,始終找不到合理的解釋。剛好就在幾周前,當我再次認真看這些內容的時候,突然發現能比較好地串起來了,所以就寫了這幾篇文章。今天,就樹哥一起與你一起重溫下這幾個知識點的聯絡與理解吧。
網上關於 Java 記憶體模型的內容特別多,很多都講到了多 CPU 與快取的資料一致性問題,於是順帶牽出了 MESI 等快取一致性協定。其實到這裡都沒問題,都挺有邏輯的。
但接下來為啥有 Java 記憶體模型?為啥又有 happens-before 原則?這些內容基本上沒有一個說得清楚,這就讓人很困惑了。此外,有些還扯出了記憶體屏障、執行時序的問題,但都沒啥邏輯,聽起來亂糟糟的。我就曾專門花了一個晚上認真看某篇很火的文章,但最終也沒搞懂。
對於 Java 記憶體模型,我捨棄了一些不必要的細碎點,整理了我的一些理解,我感覺相對來說還是比較好理解的。
首先,由於多核 CPU 和快取記憶體在存在,導致了快取一致性問題。 這個問題屬於硬體層面上的問題,而解決辦法是各種快取一致性協定。不同 CPU 採用的協定不同,MESI 是最經典的一個快取一致性協定。
其次,作業系統作為對底層硬體的抽象,自然也需要解決 CPU 快取記憶體與記憶體之間的快取一致性問題。 各個作業系統都對 CPU 快取記憶體與快取的讀寫存取過程進行抽象,最終得到的一個東西就是「記憶體模型」。
從硬體到作業系統,這個是我自己的理解,我並沒有找到一些資料提到這點。但我覺得這應該是沒有錯的。因為作業系統就是對底層硬體的抽象,而所有抽象的東西就需要定義一些概念。
對於作業系統來說,這些概念就是記憶體模型、CPU 時間片等。記憶體模型這個詞,在作業系統的教科書上也是可以找到的,這也是一個佐證吧。
於是,我們從硬體層面理解到了作業系統層面,但這跟 Java 記憶體模型有啥關係呢?
最後,Java 語言作為執行在作業系統層面的高階語言,為了解決多平臺執行的問題,在作業系統基礎上進一步抽象,得到了 Java 語言層面上的記憶體模型,其也是為了解決多執行緒情況下的資料一致性問題。
我們是因為要實現 Java 語言的「Write Once, Run Anywhere」的理念,那麼就必須解決多平臺記憶體模型不一致的問題,這樣才創造出了 Java 記憶體模型。
Java 記憶體模型規定了很多規則,如果 Java 程式能夠遵守 Java 記憶體模型的規則,那麼其寫出的程式就是並行安全的,這就是 Java 記憶體模型最大的價值。
到這裡,我們從硬體、作業系統再到語言層面,知道了 Java 記憶體模型誕生的原因,知道其誕生就是為了解決多平臺的記憶體模型統一問題,進一步其實就是多執行緒的資料一致性問題。
前面說到,為了解決多平臺的記憶體模型統一,以及多執行緒的資料一致性問題,所以有了 Java 記憶體模型。但是 Java 記憶體模型的內容太多了,基本就記不住,非常不利於程式設計人員理解,所以才有了 happens-before 原則。
所以說 happens-before 原則是對 Java 記憶體模型的簡化,讓我們更好地寫出並行程式碼。
volatile 關鍵字,其實也與 Java 記憶體模型有關係,只是很多文章都沒說清楚。
volatile 關鍵字有兩個作用,就是可見性和禁止指令重排序。但為啥它有這兩個作用呢?其實 volatile 這兩個作用的來源,就來自於 Java 記憶體模型裡對 volatile 變數定義的特殊規則。
這就是 volatile 關鍵字與 Java 記憶體模型的關係,比較簡單。
至於記憶體屏障這個詞,其實就是一個讓我們方便理解的名詞,誕生於 volatile 禁止指令重排序這個作用裡,也沒啥不好理解的。
synchronized 關鍵字,也是並行程式設計常用到的內容,其實它和 Java 記憶體模型沒關係,但和 Java 虛擬機器器規範有關係。
synchronized 關鍵字經過編譯之後,會在同步塊的前後分別形成 monitorenter 和 monitorexit 這兩個位元組碼指令,這兩個位元組碼的執行需要指明一個要鎖定或解鎖的物件。而 monitorenter 和 monitorexit 這兩個位元組碼指令為啥能實現這樣的功能,是因為 Java 虛擬機器器中做了強制定義,那麼虛擬機器器就需要實現。
synchronized 關鍵字與 Java 物件的記憶體佈局,也是有關係的。自旋鎖、自適應鎖、偏向鎖,它們靠什麼實現,就是 Java 物件中的物件頭去判斷,然後進行一系列的邏輯操作。
至此,我們基本上可以把 Java 並行程式設計裡常見的那些概念的關係搞清楚了。
Java記憶體模型 是對記憶體佈局的抽象,解決多平臺執行以及多執行緒一致性的問題。happens-before 原則 是 Java 記憶體模型定義的簡化,方便我們學習。volatile 則是輕量級同步同步機制,其來源於 Java 記憶體模型賦予的權利。
synchronized 關鍵字的合法性,則來自於 Java 虛擬機器器規範。而 synchronized 中自旋鎖、自適應鎖、偏向鎖等,都依靠 Java 物件的物件頭 來判斷。
以上就是我對 Java 並行程式設計裡常見概念的理解,感覺還是比較清晰一些。如果有什麼理解得不對的,歡迎一起探討探討~