Java 內(nèi)存模型理解

Java 內(nèi)存模型概述

Java 內(nèi)存模型是通過各種操作來定義胶果,包括對變量的讀/寫操作,監(jiān)視器的加鎖和釋放操作斤斧,以及線程啟動和合并操作早抠。JMM為程序中所有的操作定義了一個(gè)偏序關(guān)系钦无,稱之為Happens-Before洁墙。要想保證操作B的線程能看到操作A的結(jié)果(無論A和B是否在同一個(gè)線程中執(zhí)行)哑子,那么在A和B之間必須滿足Happens-Before關(guān)系告嘲。如果兩個(gè)操作之間缺乏Happens-Before關(guān)系摧冀,那么JVM可以對它們?nèi)我獾嘏判?/strong>备燃。

Java內(nèi)存模型說明了某個(gè)線程的內(nèi)存操作在哪些情況下對于其他線程是可見的辅鲸。

平臺內(nèi)存模型和 Java 內(nèi)存模型

平臺內(nèi)存模型是指硬件內(nèi)存模型治筒,在共享內(nèi)存的多處理器體系架構(gòu)中酱床,每個(gè)處理器都擁有自己的緩存羊赵,并且定期地與主內(nèi)存進(jìn)行協(xié)調(diào),如下圖所示。為了告訴應(yīng)用程序可以從內(nèi)存系統(tǒng)中獲得怎樣的保證昧捷,平臺內(nèi)存模型定義了一些特殊指令(內(nèi)存柵欄/柵欄/內(nèi)存屏障)闲昭,當(dāng)需要共享數(shù)據(jù)時(shí),這些指令就能實(shí)現(xiàn)額外的存儲協(xié)調(diào)保證靡挥。

平臺內(nèi)存模型

.

為了使Java開發(fā)人員無需關(guān)注不同平臺內(nèi)存模型的差異序矩,Java提供了自己的內(nèi)存模型(JMM)。Java內(nèi)存模型其實(shí)是定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中跋破,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory)簸淀,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是Java內(nèi)存模型的一個(gè)抽象概念毒返,并不真實(shí)存在租幕。它涵蓋了緩存,寫緩沖區(qū)拧簸,寄存器以及其他的硬件和編譯器優(yōu)化劲绪。

Java 內(nèi)存模型

.

在JVM內(nèi)部,Java內(nèi)存模型把內(nèi)存分成了兩部分:線程棧區(qū)和堆區(qū)盆赤,下圖展示了Java內(nèi)存模型在JVM中的邏輯視圖:

Java 內(nèi)存結(jié)構(gòu)

.

JVM中運(yùn)行的每個(gè)線程都擁有自己的線程棧贾富,線程棧包含了當(dāng)前線程執(zhí)行的方法調(diào)用相關(guān)信息,我們也把它稱作調(diào)用棧(虛擬機(jī)棧牺六,本地方法棧颤枪,程序計(jì)數(shù)器)。一個(gè)線程只能讀取自己的線程棧兔乞,也就是說汇鞭,線程中的本地變量對其它線程是不可見的。即使兩個(gè)線程執(zhí)行的是同一段代碼庸追,它們也會各自在自己的線程棧中創(chuàng)建本地變量霍骄,因此,每個(gè)線程中的本地變量都會有自己的版本淡溯。

堆區(qū)(堆區(qū)读整,方法區(qū))包含了Java應(yīng)用創(chuàng)建的所有對象信息,不管對象是哪個(gè)線程創(chuàng)建的咱娶,其中的對象包括原始類型的封裝類(如Byte米间、Integer、Long等等)膘侮。不管對象是屬于一個(gè)成員變量還是方法中的本地變量屈糊,它都會被存儲在堆區(qū)。

因?yàn)镴ava內(nèi)存模型和平臺底層內(nèi)存模型之間的差異琼了,Java內(nèi)存模型中的變量和對象會存儲到平臺內(nèi)存模型的各個(gè)存儲區(qū)域:

Java 內(nèi)存模型 VS 平臺內(nèi)存模型

.

多個(gè)線程對共享對象的訪問必然會面臨一些問題逻锐,其中最主要的兩個(gè)問題是:

1. 共享對象對各個(gè)線程的可見性
2. 共享對象的競爭現(xiàn)象

JVM通過在適當(dāng)?shù)奈恢蒙喜迦雰?nèi)存屏障來屏蔽Java 內(nèi)存模型與底層平臺內(nèi)存模型之間的差異夫晌,Java內(nèi)存模型并定義了一系列Happens-Before關(guān)系,用來保證某個(gè)線程的內(nèi)存操作對其他線程的可見性昧诱,并避免產(chǎn)生數(shù)據(jù)競爭晓淀。

指令重排序

在執(zhí)行程序時(shí),為了提高性能盏档,編譯器和處理器會對指令做重排序⌒钻現(xiàn)實(shí)中,沒有正確同步情況下蜈亩,各種使操作延遲或看似亂序執(zhí)行的原因懦窘,都可以歸結(jié)到重排序。編譯器和處理器進(jìn)行指令重排序時(shí)稚配,會遵守以下規(guī)律:

數(shù)據(jù)依賴性

如果兩個(gè)操作訪問同一個(gè)變量奶赠,其中一個(gè)為寫操作,此時(shí)這兩個(gè)操作之間存在依賴性药有,編譯器和處理器不會改變存在這兩個(gè)操作的執(zhí)行順序。

as-if-serial

不管怎么重排序苹丸,單線程下的執(zhí)行結(jié)果不能被改變愤惰,編譯器、runtime和處理器都必須遵守as-if-serial語義赘理。

內(nèi)存屏障(Memory Barrier )

插入一條Memory Barrier會告訴編譯器和CPU:不管什么指令都不能和這條Memory Barrier指令重排序宦言。

Memory Barrier所做的另外一件事是強(qiáng)制刷出各種CPU cache,如一個(gè)Write-Barrier(寫入屏障)將刷出所有在Barrier之前寫入 cache 的數(shù)據(jù)商模,因此奠旺,任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本。

如果一個(gè)變量是volatile修飾的施流,JMM會在寫入這個(gè)字段之后插進(jìn)一個(gè)Write-Barrier指令响疚,并在讀這個(gè)字段之前插入一個(gè)Read-Barrier指令。這意味著瞪醋,如果寫入一個(gè)volatile變量忿晕,就可以保證:

1. 一個(gè)線程寫入變量a后,任何線程訪問該變量都會拿到最新值银受。
2. 在寫入變量a之前的寫入操作践盼,其更新的數(shù)據(jù)對于其他線程也是可見的。因?yàn)镸emory Barrier會刷出cache中的所有先前的寫入宾巍。

Happens-Before

Java 內(nèi)存模型為程序中所有的操作定義了一個(gè)偏序關(guān)系咕幻,稱之為Happens-Before。當(dāng)一個(gè)變量被多個(gè)線程讀取并且至少被一個(gè)線程寫入時(shí)顶霞,如果在讀操作和寫操作之間沒有依照 Happens-Before 來排序肄程,那么久會產(chǎn)生數(shù)據(jù)競爭問題。

在正確同步的程序中不存在數(shù)據(jù)競爭,并會表現(xiàn)出串行一致性绷耍,這意味著程序中的所有操作都會按照一種固定和全局的順序執(zhí)行吐限。Happens-Before規(guī)則包括:

程序順序規(guī)則

如果程序中操作A在操作B之前,那么線程中操作A將在B操作之前執(zhí)行

監(jiān)視器規(guī)則

在監(jiān)視器鎖上的解鎖操作褂始,必須在同一個(gè)監(jiān)視器鎖上的加鎖操作之前執(zhí)行

volatile 變量規(guī)則

對 volatile 變量的寫入操作必須在對該變量的讀操作之前執(zhí)行诸典。原子變量與 volatile 變量在讀操作和寫操作上有著相同的內(nèi)存語義

線程啟動規(guī)則

在線程上對 Thread.start 的調(diào)用必須在該線程中執(zhí)行任何操作之前執(zhí)行

線程結(jié)束規(guī)則

線程中的任何操作都必須在其他線程檢測到該線程已經(jīng)結(jié)束之前執(zhí)行

中斷規(guī)則規(guī)則

當(dāng)一個(gè)線程在另一個(gè)線程上調(diào)用 interrupt 時(shí),必須在被中斷線程檢測到 interrupt調(diào)用之前執(zhí)行

終結(jié)器規(guī)則

對象的構(gòu)造函數(shù)必須在啟動該對象的終結(jié)器之前執(zhí)行完成

傳遞性規(guī)則

如果操作A在操作B之前執(zhí)行崎苗,并且操作B在操作C之前執(zhí)行狐粱,那么操作A必須在操作C之前執(zhí)行

注意:兩個(gè)操作之間具有happens-before關(guān)系,并不意味前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行胆数!僅僅要求前一個(gè)操作的執(zhí)行結(jié)果肌蜻,對于后一個(gè)操作是可見的,且前一個(gè)操作按順序排在后一個(gè)操作之前必尼。

內(nèi)容來源

Java 并發(fā)編程實(shí)戰(zhàn)

http://blog.csdn.net/suifeng3051/article/details/52611310

http://www.reibang.com/p/d3fda02d4cae

http://blog.csdn.net/ccit0519/article/details/11241403

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒋搜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子判莉,更是在濱河造成了極大的恐慌豆挽,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件券盅,死亡現(xiàn)場離奇詭異帮哈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锰镀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門娘侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泳炉,你說我怎么就攤上這事憾筏。” “怎么了花鹅?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵踩叭,是天一觀的道長。 經(jīng)常有香客問我翠胰,道長容贝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任之景,我火速辦了婚禮斤富,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锻狗。我一直安慰自己满力,他們只是感情好焕参,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著油额,像睡著了一般叠纷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潦嘶,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天涩嚣,我揣著相機(jī)與錄音,去河邊找鬼掂僵。 笑死航厚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锰蓬。 我是一名探鬼主播幔睬,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芹扭!你這毒婦竟也來了麻顶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤舱卡,失蹤者是張志新(化名)和其女友劉穎澈蚌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灼狰,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年浮禾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了交胚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盈电,死狀恐怖蝴簇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匆帚,我是刑警寧澤熬词,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站吸重,受9級特大地震影響互拾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚎幸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一颜矿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫉晶,春花似錦骑疆、人聲如沸田篇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泊柬。三九已至,卻和暖如春诈火,著一層夾襖步出監(jiān)牢的瞬間兽赁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工柄瑰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闸氮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓教沾,卻偏偏與公主長得像蒲跨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子授翻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容