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

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

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

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

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

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

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

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

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

2. JMM內存模型

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

  • 變量:這里指包括了實例字段、靜態(tài)字段和構成數組對象的元素娱仔,但是不包括局部變量與方法參數沐飘,后者是線程私有的,不會被共享牲迫。
  • 主內存:在java中耐朴,實例域、靜態(tài)域和數組元素是線程之間共享的數據盹憎,它們存儲在主內存中筛峭。
  • 工作內存:每條線程都有自己的工作內存,線程的工作內存中保存了該線程使用到的變量到主內存副本拷貝陪每,線程對變量的所有操作(讀取影晓、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變量檩禾。
線程挂签、主內存和工作內存之間交互關系

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

3. 內存間交互操作

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

  • lock(鎖定):作用于主內存的變量,把一個變量標識為一條線程獨占狀態(tài)博个。
  • unlock(解鎖):作用于主內存變量怀樟,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定盆佣。
  • read(讀韧ぁ):作用于主內存變量,把一個變量值從主內存?zhèn)鬏數骄€程的工作內存中共耍,以便隨后的load動作使用
  • load(載入):作用于工作內存的變量虑灰,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  • use(使用):作用于工作內存的變量痹兜,把工作內存中的一個變量值傳遞給執(zhí)行引擎穆咐,每當虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
  • assign(賦值):作用于工作內存的變量字旭,它把一個從執(zhí)行引擎接收到的值賦值給工作內存的變量庸娱,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
  • store(存儲):作用于工作內存的變量谐算,把工作內存中的一個變量的值傳送到主內存中熟尉,以便隨后的write的操作。
  • write(寫入):作用于主內存的變量洲脂,它把store操作從工作內存中一個變量的值傳送到主內存的變量中斤儿。

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

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

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

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

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

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

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

4.1 volatile可見性

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

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

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


volatile開關

4.2 禁止指令重排序

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

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

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

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

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

最后傻唾,關于volatile禁止重排序幾點使用說明:

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

5. JMM內存模型總結

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

5.1 原子性

原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷抱既,要么就都不執(zhí)行。
java內存模型的read扁誓、load防泵、assign、use蝗敢、store和write六個操作直接保證原子性捷泞,我們可以任務基本數據類型訪問讀寫是具有原子性(特殊說明long double64位操作根據jvm實現有關)。
如果場景中需要大范圍的原子性保證寿谴,java內存模型提供了lock和unlock操作來滿足锁右,對應到java代碼關鍵字即是——synchronized。

5.2 可見性

可見性是指當多個線程訪問同一個變量時讶泰,一個線程修改了這個變量的值咏瑟,其他線程能夠立即看得到修改的值。
除了上面介紹的volatile外痪署,java還提供了兩個關鍵字實現可見性码泞,synchronized和final。

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

5.3 有序性

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

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

參考文檔

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末摔桦,一起剝皮案震驚了整個濱河市社付,隨后出現的幾起案子承疲,更是在濱河造成了極大的恐慌,老刑警劉巖鸥咖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕鸽,死亡現場離奇詭異,居然都是意外死亡啼辣,警方通過查閱死者的電腦和手機啊研,發(fā)現死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸥拧,“玉大人党远,你說我怎么就攤上這事沟娱』ǔ粒” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柿赊。 經常有香客問我碰声,道長胰挑,這世上最難降的妖魔是什么瞻颂? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任厂榛,我火速辦了婚禮,結果婚禮上吐根,老公的妹妹穿的比我還像新娘局义。我一直安慰自己萄唇,他們只是感情好湃密,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布忿危。 她就那樣靜靜地躺著铺厨,像睡著了一般解滓。 火紅的嫁衣襯著肌膚如雪洼裤。 梳的紋絲不亂的頭發(fā)上逸邦,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天雷客,我揣著相機與錄音皱卓,去河邊找鬼娜汁。 笑死,一個胖子當著我的面吹牛傅事,可吹牛的內容都是我干的。 我是一名探鬼主播响鹃,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钦听?” 一聲冷哼從身側響起秦踪,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤景馁,失蹤者是張志新(化名)和其女友劉穎绰精,沒想到半個月后撒璧,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡笨使,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年卿樱,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硫椰。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡繁调,死狀恐怖,靈堂內的尸體忽然破棺而出最爬,到底是詐尸還是另有隱情涉馁,我是刑警寧澤门岔,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布爱致,位于F島的核電站,受9級特大地震影響寒随,放射性物質發(fā)生泄漏糠悯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一妻往、第九天 我趴在偏房一處隱蔽的房頂上張望互艾。 院中可真熱鬧,春花似錦讯泣、人聲如沸纫普。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昨稼。三九已至,卻和暖如春拳锚,著一層夾襖步出監(jiān)牢的瞬間假栓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工霍掺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匾荆,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓杆烁,卻偏偏與公主長得像牙丽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兔魂,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內容

  • 幼來無助伶仃苦烤芦, 夢魘年年。 夢魘年年入热, 強作人前展笑顏拍棕。 二十雙喜為人母晓铆, 病痛綿綿。 病痛綿綿绰播, 笑里朦朧淚水咸骄噪。
    清勇盧追閱讀 302評論 0 0
  • 師弟問師兄如何才能長生不老链蕊,師兄緩緩道:“忘情、無我谬泌,浮游滄海之間滔韵,百無牽掛,能與天地同壽掌实∨泸撸”師弟若有所思地去了。...
    洞庭府君閱讀 283評論 0 4
  • 2016年是抑郁癥困擾的一年贱鼻,經歷了連續(xù)痛苦的失眠宴卖,經常被恐懼驚醒。抑郁癥影響了我的各個方面邻悬,幾乎喪失了基本的社交...
    871263354579閱讀 133評論 0 0
  • 所謂“成人的世界攘烛,智商在一個層次上,才能在一起玩”屁桑。相信看過《歡樂頌》的人都對趙醫(yī)生嫌棄曲筱綃無知的事情印象深刻吧...
    秦楚zoro閱讀 19,779評論 2 13