GitHub 4.1k Star 的Java工程師成神之路 ,不來了解一下嗎?
GitHub 4.1k Star 的Java工程師成神之路 ,真的不來了解一下嗎?
GitHub 4.1k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?
最近,面試過很多Java中高階開發,問過很多次關於Java記憶體模型的知識,問完之後,很多人上來就開始回答:
Java記憶體模型由幾部分組成,堆、本地方法棧、虛擬機器棧、方法區...
每一次我不想打斷他們的話,雖然我知道這又是一個誤會了我的問題的朋友。
其實,我想問的Java記憶體模型,是和併發程式設計有關的。而候選人給我回答的那叫JVM記憶體結構,完全是兩回事。
很多時候,在我沒有打斷他們的情況下,一部分人慢慢的講到了GC相關的知識。這種情況下,我只能硬著頭皮繼續問一些和JVM有關的知識。
但是,我的本意其實是想看一下他對Java併發有多少了解啊。
經常,我都在繼續追問了一些他們回答的"Java記憶體模型"相關的知識後,友善的提醒一句,其實我想問的Java記憶體模型並不是他回答的這個...
有的時候,我會進一步提醒一句:是和併發程式設計有關的,是和主記憶體以及執行緒工作記憶體有關的。。。
那麼,本文就來簡單說一說,關於Java記憶體模型,到底應該如何回答這個面試題。
為什麼會誤解
首先,我們先來分析一下問什麼很多人,甚至是大多數人會答非所問呢?
我覺得主要有幾個原因:
1、Java記憶體模型,這個詞聽著太像是關於記憶體分佈的知識了。聽上去和併發程式設計沒有半毛錢關係。
2、網上很多資料都是錯的。不信你去網上搜尋一下"Java記憶體模型",你會發現,很多人打著記憶體模型的標題,介紹了JVM記憶體結構的知識。
這裡提一句,我嘗試著Google搜尋了一下搜尋"Java記憶體模型",首頁展示結果如下:

首頁排名靠前的5篇文章中,有1篇是錯的,介紹了JVM記憶體結構。
PS:值得慶幸的的是,首頁前5篇文章中,有兩篇是我寫的,至少我的這兩篇我敢確定是不具備任何誤導性的!!
3、還存在一種情況,雖然不多見,但是也有。那就是很多面試官自己也以為記憶體模型就是要介紹堆、棧、方法區這些知識。就導致有時候面試者不知道自己到底應該如何回答。
那麼,到底什麼是Java記憶體模型?關於這道面試題應該如何回答呢?
什麼是記憶體模型
我曾經在《再有人問你Java記憶體模型是什麼,就把這篇文章發給他》中詳細的介紹過Java記憶體模型的來龍去脈,這裡再重新回顧一下。
Java記憶體模型是根據英文Java Memory Model(JMM)翻譯過來的。其實JMM並不像JVM記憶體結構一樣是真實存在的。他只是一個抽象的概念。
Java記憶體模型的相關知識在 JSR-133: Java Memory Model and Thread Specification 中描述的。JMM是和多執行緒相關的,他描述了一組規則或規範,這個規範定義了一個執行緒對共享變數的寫入時對另一個執行緒是可見的。
Java記憶體模型(Java Memory Model ,JMM)就是一種符合記憶體模型規範的,遮蔽了各種硬體和作業系統的訪問差異的,保證了Java程式在各種平臺下對記憶體的訪問都能得到一致效果的機制及規範。目的是解決由於多執行緒通過共享記憶體進行通訊時,存在的原子性、可見性(快取一致性)以及有序性問題。
那麼,我們這裡就先來說說什麼是所謂的記憶體模型規範、這裡提到的原子性、可見性以及有序性又是什麼東西?
原子性
執行緒是CPU排程的基本單位。CPU有時間片的概念,會根據不同的排程演算法進行執行緒排程。所以在多執行緒場景下,就會發生原子性問題。因為執行緒在執行一個讀改寫操作時,在執行完讀改之後,時間片耗完,就會被要求放棄CPU,並等待重新排程。這種情況下,讀改寫就不是一個原子操作。即存在原子性問題。
快取一致性
在多核CPU,多執行緒的場景中,每個核都至少有一個L1 快取。多個執行緒訪問程式中的某個共享記憶體,且這多個執行緒分別在不同的核心上執行,則每個核心都會在各自的caehe中保留一份共享記憶體的緩衝。由於多核是可以並行的,可能會出現多個執行緒同時寫各自的快取的情況,而各自的cache之間的資料就有可能不同。
在CPU和主存之間增加快取,在多執行緒場景下就可能存在快取一致性問題,也就是說,在多核CPU中,每個核的自己的快取中,關於同一個資料的快取內容可能不一致。
有序性
除了引入了時間片以外,由於處理器優化和指令重排等,CPU還可能對輸入程式碼進行亂序執行,比如load->add->save 有可能被優化成load->save->add 。這就是有序性問題。
多CPU多級快取導致的一致性問題、CPU時間片機制導致的原子性問題、以及處理器優化和指令重排導致的有序性問題等,都硬體的不斷升級導致的。那麼,有沒有什麼機制可以很好的解決上面的這些問題呢?
最簡單直接的做法就是廢除處理器和處理器的優化技術、廢除CPU快取,讓CPU直接和主存互動。但是,這麼做雖然可以保證多執行緒下的併發問題。但是,這就有點因噎廢食了。
所以,為了保證併發程式設計中可以滿足原子性、可見性及有序性。有一個重要的概念,那就是——記憶體模型。
為了保證共享記憶體的正確性(可見性、有序性、原子性),記憶體模型定義了共享記憶體系統中多執行緒程式讀寫操作行為的規範。通過這些規則來規範對記憶體的讀寫操作,從而保證指令執行的正確性。它與處理器有關、與快取有關、與併發有關、與編譯器也有關。他解決了CPU多級快取、處理器優化、指令重排等導致的記憶體訪問問題,保證了併發場景下的一致性、原子性和有序性。
針對上面的這些問題,不同的作業系統都有不同的解決方案,而Java語言為了遮蔽掉底層的差異,定義了一套屬於Java語言的記憶體模型規範,即Java記憶體模型。
Java記憶體模型規定了所有的變數都儲存在主記憶體中,每條執行緒還有自己的工作記憶體,執行緒的工作記憶體中儲存了該執行緒中是用到的變數的主記憶體副本拷貝,執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體。不同的執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數的傳遞均需要自己的工作記憶體和主存之間進行資料同步進行。
而JMM就作用於工作記憶體和主存之間資料同步過程。他規定了如何做資料同步以及什麼時候做資料同步。

Java記憶體模型的實現
瞭解Java多執行緒的朋友都知道,在Java中提供了一系列和併發處理相關的關鍵字,比如volatile、synchronized、final、concurren包等。其實這些就是Java記憶體模型封裝了底層的實現後提供給程式設計師使用的一些關鍵字。
在開發多執行緒的程式碼的時候,我們可以直接使用synchronized等關鍵字來控制併發,從來就不需要關心底層的編譯器優化、快取一致性等問題。所以,Java記憶體模型,除了定義了一套規範,還提供了一系列原語,封裝了底層實現後,供開發者直接使用。
本文並不準備把所有的關鍵字逐一介紹其用法,因為關於各個關鍵字的用法,網上有很多資料。讀者可以自行學習。本文還有一個重點要介紹的就是,我們前面提到,併發程式設計要解決原子性、有序性和一致性的問題,我們就再來看下,在Java中,分別使用什麼方式來保證。
原子性
在Java中,為了保證原子性,提供了兩個高階的位元組碼指令monitorenter和monitorexit。在synchronized的實現原理文章中,介紹過,這兩個位元組碼,在Java中對應的關鍵字就是synchronized。
因此,在Java中可以使用synchronized來保證方法和程式碼塊內的操作是原子性的。
可見性
Java記憶體模型是通過在變數修改後將新值同步回主記憶體,在變數讀取前從主記憶體重新整理變數值的這種依賴主記憶體作為傳遞媒介的方式來實現的。
Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變數在被修改後可以立即同步到主記憶體,被其修飾的變數在每次是用之前都從主記憶體重新整理。因此,可以使用volatile來保證多執行緒操作時變數的可見性。
除了volatile,Java中的synchronized和final兩個關鍵字也可以實現可見性。只不過實現方式不同,這裡不再展開了。
有序性
在Java中,可以使用synchronized和volatile來保證多執行緒之間操作的有序性。實現方式有所區別:
volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條執行緒操作。
好了,這裡簡單的介紹完了Java併發程式設計中解決原子性、可見性以及有序性可以使用的關鍵字。讀者可能發現了,好像synchronized關鍵字是萬能的,他可以同時滿足以上三種特性,這其實也是很多人濫用synchronized的原因。
但是synchronized是比較影響效能的,雖然編譯器提供了很多鎖優化技術,但是也不建議過度使用。
面試如何回答
前面我介紹完了一些和Java記憶體模型有關的基礎知識,只是基礎,並不是全部,因為隨便一個知識點還是都可以展開的,如volatile是如何實現可見性的?synchronized是如何實現有序性的?
但是,當面試官問你:能簡單介紹下你理解的記憶體模型嗎?
首先,先和麵試官確認一下:您說的記憶體模型指的是JMM,也就是和併發程式設計有關的那一個吧?
在得到肯定答覆後,再開始介紹(如果不是,那可能就要回答堆、棧、方法區哪些了....囧...):
Java記憶體模型,其實是保證了Java程式在各種平臺下對記憶體的訪問都能夠得到一致效果的機制及規範。目的是解決由於多執行緒通過共享記憶體進行通訊時,存在的原子性、可見性(快取一致性)以及有序性問題。
除此之外,Java記憶體模型還提供了一系列原語,封裝了底層實現後,供開發者直接使用。如我們常用的一些關鍵字:synchronized、volatile以及併發包等。
回答到這裡就可以了,然後面試官可能會繼續追問,然後根據他的追問再繼續往下回答即可。
所以,當有人再問你Java記憶體模型的時候,不要一張嘴就直接回答堆疊、方法區甚至GC了,那樣顯得很不專業!