java并發(fā)編程——內(nèi)存模型

1. 并發(fā)編程基礎(chǔ)概念

并發(fā)——在操作系統(tǒng)中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行——源自百度百科

在并發(fā)編程中,我們需要處理兩個關(guān)鍵問題:線程之間如何通信線程之間如何同步,后續(xù)篇章將圍繞這兩個問題進行介紹。

  • 線程通信:是指線程之間以何種機制來交換信息,在命令式編程中,線程之間的通信機制有兩種:共享內(nèi)存和消息傳遞
  • 線程同步:是指程序用于控制不同線程之間操作發(fā)生相對順序的機制。在Java中,可以通過volatile,synchronized, 鎖等方式實現(xiàn)同步。

本文主要介紹java的通信機制,剛介紹常見通信機制主要包括以下兩種方式:

  1. 共享內(nèi)存:線程之間共享程序的公共狀態(tài),線程之間通過寫-讀內(nèi)存中的公共狀態(tài)來隱式進行通信。
  2. 消息傳遞:線程之間沒有公共狀態(tài),線程之間必須通過明確的發(fā)送消息來顯式進行通信。

Java的并發(fā)采用的是共享內(nèi)存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。在java中,所有實例域、靜態(tài)域和數(shù)組元素存儲在堆內(nèi)存中,堆內(nèi)存在線程之間共享。

Java線程之間的通信由Java內(nèi)存模型(本文簡稱為JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。

2. JMM內(nèi)存模型

JMM(Java Memory Model)是JVM規(guī)范中定義的一種Java內(nèi)存模型,它的目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺上到能達到一致的內(nèi)存訪問效果。
Java內(nèi)存模型的主要定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣底層細節(jié)。首先簡單說明幾個常用名稱定義:

  • 變量:這里指包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但是不包括局部變量與方法參數(shù),后者是線程私有的,不會被共享。
  • 主內(nèi)存:在java中,實例域、靜態(tài)域和數(shù)組元素是線程之間共享的數(shù)據(jù),它們存儲在主內(nèi)存中。
  • 工作內(nèi)存:每條線程都有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存副本拷貝,線程對變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量。
線程、主內(nèi)存和工作內(nèi)存之間交互關(guān)系

線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如上圖所示,和CPU-緩存-內(nèi)存很類似。
不同線程之間無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來完成。
最后注意,為了獲得較好的執(zhí)行性能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來提升指令執(zhí)行速度,也沒有限制編譯器對指令進行重排序。也就是說,在java內(nèi)存模型中,也會存在緩存一致性問題和指令重排序的問題

3. 內(nèi)存間交互操作

關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步到主內(nèi)存之間的實現(xiàn)細節(jié),Java內(nèi)存模型定義了以下八種操作來完成:

  • lock(鎖定):作用于主內(nèi)存的變量,把一個變量標識為一條線程獨占狀態(tài)。
  • unlock(解鎖):作用于主內(nèi)存變量,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
  • read(讀?。鹤饔糜谥鲀?nèi)存變量,把一個變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用
  • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
  • use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎,每當虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
  • store(存儲):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中,以便隨后的write的操作。
  • write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個變量的值傳送到主內(nèi)存的變量中。

所以變量讀寫包含以下幾個步驟:

  1. 變量從主內(nèi)存復(fù)制到工作內(nèi)存——順序執(zhí)行read和load操作
  2. 變量從工作內(nèi)存同步到主內(nèi)存——順序執(zhí)行store和write操作

注意,Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行。也就是read和load之間,store和write之間是可以插入其他指令的。
除了定義以上8中原子操作,Java內(nèi)存模型還規(guī)定了上述8種基本操作在執(zhí)行時必須滿足一定的操作規(guī)則,例如如不允許read和load單獨出現(xiàn)(即不允許一個變量從主內(nèi)存中讀取但工作內(nèi)存不接受),不允許store和write單獨出現(xiàn)(即不允許從工作內(nèi)存中發(fā)起了回寫單主內(nèi)存不接受),這里不一一列舉,詳細網(wǎng)上搜索即可。
Java內(nèi)存模型還定義了volatile型變量的特殊規(guī)則(下一節(jié)介紹),以上三種規(guī)定共同確定了Java中哪些內(nèi)存訪問操作是安全的即:

8種原子操作+操作規(guī)則+volatile規(guī)定=Java中哪些內(nèi)存訪問操作是安全的

4. volatile型變量規(guī)定

當一個變量被定義為volatile后,將具備兩種特性:

  • 特性一:保證此變量對所有線程的可見性
  • 特性二:禁止指令重排序優(yōu)化

4.1 volatile可見性

可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內(nèi)存中可能還是原來的舊值,因此無法保證可見性。
但是,需要注意的是volatile變量只保證可見性,但是java里面的運算并非全部都是原子操作例如++操作,這樣同樣導(dǎo)致volatile修飾變量java運算不安全。
一般不符合以下兩條規(guī)則的運算場景中,我們需要通過加鎖(synchronized或并發(fā)包中的鎖)保證變量原子性:

  • 運算結(jié)果并不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值(比如++操作不符合依賴當前值)
  • 變量不需要與其他狀態(tài)變量共同參與不變約束

常見的volatile修飾變量的場景是用來作為開關(guān)控制并發(fā):


volatile開關(guān)

4.2 禁止指令重排序

重排序:是指“編譯器和處理器”為了提高性能,而在程序執(zhí)行時會對程序進行的重排序。大致可以分為以下三類:

  • 編譯器優(yōu)化指令重排,不改變單線程語義的情況下,重新安排指令執(zhí)行的順序。
  • 指令級并行重排序,該優(yōu)化主要是為了讓程序發(fā)揮現(xiàn)代處理器的指令級并行執(zhí)行能力,前提是這些語句不存在數(shù)據(jù)依賴。
  • 內(nèi)存系統(tǒng)重排序,主要發(fā)生在處理器讀寫緩沖區(qū),讀寫過程看起來是無序的,但最終結(jié)果是有序的
    從Java源代碼到最終實際執(zhí)行的指令序列,會經(jīng)過下面三種重排序:
實際執(zhí)行指令序列

以上重排序可能會導(dǎo)致多線程中出現(xiàn)內(nèi)存可見性問題,針對編譯器重排序JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序。
而對于后兩種重排序,JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障(memory barriers,intel稱之為memory fence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。

下面我們看下jvm如何實現(xiàn)volatile禁止指令重排序的:

  1. volatile變量寫操作,jvm會向處理器發(fā)送一條Lock前綴命令,將變量所在的緩存行系會到系統(tǒng)內(nèi)存。其他處理器通過嗅探總線上傳播的數(shù)據(jù)檢測自己的數(shù)據(jù)是否過期,如果發(fā)現(xiàn)過期會置為無效,再次使用時會從系統(tǒng)內(nèi)存獲取
  2. Lock前綴命令禁止該指令與之前和之后的讀和寫指令重排序。

最后,關(guān)于volatile禁止重排序幾點使用說明:

  • 不會對volatile讀與volatile讀后面的任意內(nèi)存操作重排序
  • 不會對volatile寫與volatile寫之前的任意內(nèi)存操作重排序
  • CAS同時具有volatile讀和寫內(nèi)存的語義,java的CAS使用現(xiàn)代處理器提供的高效級別的原子指令,這些原子指令以原子方式對內(nèi)存執(zhí)行讀-改-寫操作,這是多處理器中實現(xiàn)同步的關(guān)鍵。

5. JMM內(nèi)存模型總結(jié)

總的來說JMM內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性三個特征來建立的。下面就三個特征分別說明:

5.1 原子性

原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
java內(nèi)存模型的read、load、assign、use、store和write六個操作直接保證原子性,我們可以任務(wù)基本數(shù)據(jù)類型訪問讀寫是具有原子性(特殊說明long double64位操作根據(jù)jvm實現(xiàn)有關(guān))。
如果場景中需要大范圍的原子性保證,java內(nèi)存模型提供了lock和unlock操作來滿足,對應(yīng)到j(luò)ava代碼關(guān)鍵字即是——synchronized。

5.2 可見性

可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
除了上面介紹的volatile外,java還提供了兩個關(guān)鍵字實現(xiàn)可見性,synchronized和final。

  • final的可見性:是指被final修飾的字段在構(gòu)造器中一旦完成,那么在其他線程就可以看見final字段值
  • synchronized可見性:是指對一個變量執(zhí)行unlock操作之前,必須先把次變量同步會主內(nèi)存這條操作規(guī)則限制

5.3 有序性

有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
java中天然有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個線程中觀察另外一個線程,所有操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行語義”,后半句表示“指令重排”和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。
java提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性,這里synchronized則是有“同一時刻只允許一條線程對其進行l(wèi)ock操作”這條操作規(guī)定獲取的,這個規(guī)則決定了同一個鎖的兩個同步塊只能串行進入。

最后,可以發(fā)現(xiàn)synchronized關(guān)鍵字可以同時解決上述三個問題,當然這個需要付出代價就是性能問題。

參考文檔

《深入理解java虛擬機》——周志明
http://www.cnblogs.com/dolphin0520/p/3920373.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 幼來無助伶仃苦, 夢魘年年。 夢魘年年, 強作人前展笑顏。 二十雙喜為人母, 病痛綿綿。 病痛綿綿, 笑里朦朧淚水咸。
    清勇盧追閱讀 392評論 0 0
  • 師弟問師兄如何才能長生不老,師兄緩緩道:“忘情、無我,浮游滄海之間,百無牽掛,能與天地同壽?!睅煹苋粲兴嫉厝チ?。...
    洞庭府君閱讀 357評論 0 4
  • 2016年是抑郁癥困擾的一年,經(jīng)歷了連續(xù)痛苦的失眠,經(jīng)常被恐懼驚醒。抑郁癥影響了我的各個方面,幾乎喪失了基本的社交...
    871263354579閱讀 169評論 0 0
  • 所謂“成人的世界,智商在一個層次上,才能在一起玩”。相信看過《歡樂頌》的人都對趙醫(yī)生嫌棄曲筱綃無知的事情印象深刻吧...
    秦楚zoro閱讀 20,610評論 2 13

友情鏈接更多精彩內(nèi)容