天天看點

求你了,再問你Java記憶體模型的時候别再給我講堆棧方法區了…

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記憶體模型",首頁展示結果如下:

求你了,再問你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多線程的朋友都知道,在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了,那樣顯得很不專業!

歡迎關注公衆号:

Hollis

繼續閱讀