Java 內(nèi)存模型,或許應(yīng)該這么理解
今天,就樹哥一起與你一起重溫下這幾個(gè)知識(shí)點(diǎn)的聯(lián)系與理解吧。
Java 內(nèi)存模型
網(wǎng)上關(guān)于 Java 內(nèi)存模型的內(nèi)容特別多,很多都講到了多 CPU 與緩存的數(shù)據(jù)一致性問題,于是順帶牽出了 MESI 等緩存一致性協(xié)議。其實(shí)到這里都沒問題,都挺有邏輯的。
但接下來為啥有 Java 內(nèi)存模型?為啥又有 happens-before 原則?這些內(nèi)容基本上沒有一個(gè)說得清楚,這就讓人很困惑了。此外,有些還扯出了內(nèi)存屏障、執(zhí)行時(shí)序的問題,但都沒啥邏輯,聽起來亂糟糟的。我就曾專門花了一個(gè)晚上認(rèn)真看某篇很火的文章,但最終也沒搞懂。
對(duì)于 Java 內(nèi)存模型,我舍棄了一些不必要的細(xì)碎點(diǎn),整理了我的一些理解,我感覺相對(duì)來說還是比較好理解的。
首先,由于多核 CPU 和高速緩存在存在,導(dǎo)致了緩存一致性問題。 這個(gè)問題屬于硬件層面上的問題,而解決辦法是各種緩存一致性協(xié)議。不同 CPU 采用的協(xié)議不同,MESI 是最經(jīng)典的一個(gè)緩存一致性協(xié)議。
其次,操作系統(tǒng)作為對(duì)底層硬件的抽象,自然也需要解決 CPU 高速緩存與內(nèi)存之間的緩存一致性問題。 各個(gè)操作系統(tǒng)都對(duì) CPU 高速緩存與緩存的讀寫訪問過程進(jìn)行抽象,最終得到的一個(gè)東西就是「內(nèi)存模型」。
從硬件到操作系統(tǒng),這個(gè)是我自己的理解,我并沒有找到一些資料提到這點(diǎn)。但我覺得這應(yīng)該是沒有錯(cuò)的。因?yàn)椴僮飨到y(tǒng)就是對(duì)底層硬件的抽象,而所有抽象的東西就需要定義一些概念。
對(duì)于操作系統(tǒng)來說,這些概念就是內(nèi)存模型、CPU 時(shí)間片等。內(nèi)存模型這個(gè)詞,在操作系統(tǒng)的教科書上也是可以找到的,這也是一個(gè)佐證吧。
于是,我們從硬件層面理解到了操作系統(tǒng)層面,但這跟 Java 內(nèi)存模型有啥關(guān)系呢?
最后,Java 語言作為運(yùn)行在操作系統(tǒng)層面的高級(jí)語言,為了解決多平臺(tái)運(yùn)行的問題,在操作系統(tǒng)基礎(chǔ)上進(jìn)一步抽象,得到了 Java 語言層面上的內(nèi)存模型,其也是為了解決多線程情況下的數(shù)據(jù)一致性問題。
我們是因?yàn)橐獙?shí)現(xiàn) Java 語言的「Write Once, Run Anywhere」的理念,那么就必須解決多平臺(tái)內(nèi)存模型不一致的問題,這樣才創(chuàng)造出了 Java 內(nèi)存模型。
Java 內(nèi)存模型規(guī)定了很多規(guī)則,如果 Java 程序能夠遵守 Java 內(nèi)存模型的規(guī)則,那么其寫出的程序就是并發(fā)安全的,這就是 Java 內(nèi)存模型最大的價(jià)值。
到這里,我們從硬件、操作系統(tǒng)再到語言層面,知道了 Java 內(nèi)存模型誕生的原因,知道其誕生就是為了解決多平臺(tái)的內(nèi)存模型統(tǒng)一問題,進(jìn)一步其實(shí)就是多線程的數(shù)據(jù)一致性問題。
happens-before 原則
前面說到,為了解決多平臺(tái)的內(nèi)存模型統(tǒng)一,以及多線程的數(shù)據(jù)一致性問題,所以有了 Java 內(nèi)存模型。但是 Java 內(nèi)存模型的內(nèi)容太多了,基本就記不住,非常不利于編程人員理解,所以才有了 happens-before 原則。
所以說 happens-before 原則是對(duì) Java 內(nèi)存模型的簡化,讓我們更好地寫出并發(fā)代碼。
volatile 關(guān)鍵字
volatile 關(guān)鍵字,其實(shí)也與 Java 內(nèi)存模型有關(guān)系,只是很多文章都沒說清楚。
volatile 關(guān)鍵字有兩個(gè)作用,就是可見性和禁止指令重排序。但為啥它有這兩個(gè)作用呢?其實(shí) volatile 這兩個(gè)作用的來源,就來自于 Java 內(nèi)存模型里對(duì) volatile 變量定義的特殊規(guī)則。
這就是 volatile 關(guān)鍵字與 Java 內(nèi)存模型的關(guān)系,比較簡單。
至于內(nèi)存屏障這個(gè)詞,其實(shí)就是一個(gè)讓我們方便理解的名詞,誕生于 volatile 禁止指令重排序這個(gè)作用里,也沒啥不好理解的。
synchronized 關(guān)鍵字
synchronized 關(guān)鍵字,也是并發(fā)編程常用到的內(nèi)容,其實(shí)它和 Java 內(nèi)存模型沒關(guān)系,但和 Java 虛擬機(jī)規(guī)范有關(guān)系。
synchronized 關(guān)鍵字經(jīng)過編譯之后,會(huì)在同步塊的前后分別形成 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令,這兩個(gè)字節(jié)碼的執(zhí)行需要指明一個(gè)要鎖定或解鎖的對(duì)象。
而 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令為啥能實(shí)現(xiàn)這樣的功能,是因?yàn)?Java 虛擬機(jī)中做了強(qiáng)制定義,那么虛擬機(jī)就需要實(shí)現(xiàn)。
synchronized 關(guān)鍵字與 Java 對(duì)象的內(nèi)存布局,也是有關(guān)系的。自旋鎖、自適應(yīng)鎖、偏向鎖,它們靠什么實(shí)現(xiàn),就是 Java 對(duì)象中的對(duì)象頭去判斷,然后進(jìn)行一系列的邏輯操作。
總結(jié)
至此,我們基本上可以把 Java 并發(fā)編程里常見的那些概念的關(guān)系搞清楚了。
Java 內(nèi)存模型 是對(duì)內(nèi)存布局的抽象,解決多平臺(tái)運(yùn)行以及多線程一致性的問題。 happens-before 原則 是 Java 內(nèi)存模型定義的簡化,方便我們學(xué)習(xí)。 volatile 則是輕量級(jí)同步同步機(jī)制,其來源于 Java 內(nèi)存模型賦予的權(quán)利。
synchronized 關(guān)鍵字的合法性,則來自于 Java 虛擬機(jī)規(guī)范。而 synchronized 中自旋鎖、自適應(yīng)鎖、偏向鎖等,都依靠 Java 對(duì)象的對(duì)象頭 來判斷。
以上就是我對(duì) Java 并發(fā)編程里常見概念的理解,感覺還是比較清晰一些。