多線程積累:JMM模型

(一)前言

學(xué)習(xí)多線程,要理解java內(nèi)存模型贱案,才能理解多線程情況下肛炮,數(shù)據(jù)的變化止吐,指令的運(yùn)行等宝踪,才能更好的了解多線程的運(yùn)行情況和日常使用的注意點(diǎn)。

(二)JMM與硬件內(nèi)存結(jié)構(gòu)

java內(nèi)存模型與硬件內(nèi)存結(jié)構(gòu).png

如上圖所示碍扔,可以看到JMM的大概結(jié)構(gòu)與硬件內(nèi)存結(jié)構(gòu)之間的關(guān)系瘩燥,每個(gè)線程只能訪問自己工作內(nèi)存的數(shù)據(jù),工作內(nèi)存中存儲(chǔ)著主內(nèi)存中變量復(fù)制的副本不同,這兩個(gè)內(nèi)存的數(shù)據(jù)可以存儲(chǔ)在硬件內(nèi)存中的任一地方厉膀,并沒有特殊劃分。
JMM只是一種抽象的概念二拐,是一種規(guī)則服鹅,并不真實(shí)存在,對(duì)于計(jì)算機(jī)而言百新,并不劃分工作內(nèi)存和主內(nèi)存企软,而是都存儲(chǔ)在計(jì)算機(jī)主內(nèi)存中。

(三)JMM的三種特性

1.原子性

在多線程環(huán)境下饭望,一個(gè)操作一旦開始就不會(huì)被其他線程影響仗哨。
比如一個(gè)靜態(tài)變量,被兩個(gè)線程同時(shí)進(jìn)行操作铅辞,無論如何運(yùn)行厌漂,最后的結(jié)構(gòu)必定是兩個(gè)線程中的一種結(jié)果。
特例:32位的系統(tǒng)斟珊,如果操作long或者double苇倡,由于操作位數(shù)問題,最終的結(jié)果可能并不是兩個(gè)線程中的任一結(jié)果。
其實(shí)旨椒,在上述描述中胜嗓,有一點(diǎn)無論如何運(yùn)行,在計(jì)算機(jī)執(zhí)行程序中钩乍,為了提高性能辞州,編譯器和處理器會(huì)對(duì)指令進(jìn)行重排。

指令重排
  • (1)編譯器重排
    簡(jiǎn)單的舉個(gè)例子:
    主線程:
d=3寥粹;
c=3变过;

線程A:

a=c;
d=1;

線程B:

b=d;
c=2;

在以上兩個(gè)線程之前,對(duì)c和d進(jìn)行賦值涝涤,從程序的執(zhí)行順序來說媚狰,似乎不可能存在a=2,b=1的情況,但是指令重排之后阔拳,可能存在:
線程A:

d=1;
a=c;

線程B:

c=2;
b=d;

此時(shí)崭孤,看起來就更可能存在a=2,b=1的情況,所以糊肠,多線程情況下辨宠,對(duì)變量能否保持一致是不可預(yù)知的。

  • (2)處理器重排
    簡(jiǎn)單舉個(gè)例子:
a=b+c;
d=a-e

在上述代碼里面货裹,落實(shí)到指令可以理解為:

  • 1.把b的值加載到寄存器
  • 2.把c的值加載到寄存器
  • 3.將b和c相加得到a
  • 4.將a加載到寄存器
  • 5.把e的值加載到寄存器
  • 6.將a減e得到d
  • 7.將d加載到寄存器嗤形。

其實(shí)上面的指令有個(gè)優(yōu)化的點(diǎn),就是將步驟5提前到2之后弧圆,因?yàn)椴襟E3和4都需要前面數(shù)據(jù)準(zhǔn)備好之后才能進(jìn)行赋兵,所以會(huì)進(jìn)行中斷,此時(shí)中斷搔预,會(huì)影響5的運(yùn)行霹期,將5提前,可以提高CPU的性能拯田。
重排保證了串行語(yǔ)義的執(zhí)行历造,但是在多線程的環(huán)境下,這樣是毀滅性的勿锅,導(dǎo)致結(jié)果的不可預(yù)知性帕膜。

如下代碼:

class MixedOrder{
    int a = 0;
    boolean flag = false;
    public void writer(){
        a = 1;
        flag = true;
    }

    public void read(){
        if(flag){
            int i = a + 1;
        }
    }
}

在單線程的場(chǎng)景下溢十,先調(diào)用writer()垮刹,再次調(diào)用read(),得到的結(jié)果是i=2张弛。
在多線程的場(chǎng)景下荒典,指令重排之后酪劫,read()方法在讀到flagtrue的情況下,可能誤讀a=0寺董,此時(shí)得到的結(jié)果為i=1覆糟。

2.有序性

有序性是指在單線程的執(zhí)行代碼,我們可以認(rèn)為代碼的執(zhí)行是按照順序執(zhí)行的遮咖,但是在多線程場(chǎng)景下滩字,因?yàn)橹噶钪嘏牛瑢?dǎo)致最終的指令可能是亂序的御吞,在本線程內(nèi)麦箍,所有操作都視為有序的,但是多線程下陶珠,存在共享變量挟裂,一個(gè)線程需要觀察另一個(gè)線程,所以操作都是無序的揍诽。

3.可見性

可見性指的是當(dāng)一個(gè)線程修改了某個(gè)共享變量的值诀蓉,其他線程是否能夠馬上得知這個(gè)修改的值。這個(gè)概念僅代表在并發(fā)程序上的概念暑脆。由于每個(gè)線程會(huì)將共享變量拷貝到自己的工作線程中渠啤,由于指令重排的情況,也會(huì)存在可見性的問題饵筑,導(dǎo)致結(jié)果不是預(yù)期的結(jié)果埃篓。

(四)JMM提供的解決方案

針對(duì)以上的三種特性在多線程環(huán)境下的問題,JMM提供了相應(yīng)的解決方案根资。

  • 原子性問題
    除了JVM自身提供的對(duì)基本數(shù)據(jù)類型讀寫操作的原子性外,對(duì)于方法級(jí)別或者代碼塊級(jí)別的原子性操作同窘,可以使用synchronized關(guān)鍵字或者重入鎖(ReentrantLock)保證程序執(zhí)行的原子性玄帕。
  • 可見性問題
    可見性問題,可以使用synchronized關(guān)鍵字或者volatile關(guān)鍵字解決想邦,它們都可以使一個(gè)線程修改后的變量立即對(duì)其他線程可見裤纹。
  • 有序性問題
    對(duì)于指令重排導(dǎo)致的可見性問題和有序性問題,則可以利用volatile關(guān)鍵字解決丧没,因?yàn)関olatile的另外一個(gè)作用就是禁止重排序優(yōu)化鹰椒,關(guān)于volatile稍后會(huì)進(jìn)一步分析。

同時(shí)呕童,JMM內(nèi)部還定義一套happens-before 原則來保證多線程環(huán)境下兩個(gè)操作間的原子性漆际、可見性以及有序性。

happens-before 原則

  • 1.程序順序原則
    即在一個(gè)線程內(nèi)必須保證語(yǔ)義串行性夺饲,也就是說按照代碼順序執(zhí)行奸汇。
  • 2.鎖規(guī)則
    解鎖(unlock)操作必然發(fā)生在后續(xù)的同一個(gè)鎖的加鎖(lock)之前施符,也就是說,如果對(duì)于一個(gè)鎖解鎖后擂找,再加鎖戳吝,那么加鎖的動(dòng)作必須在解鎖動(dòng)作之后(同一個(gè)鎖)。
  • 3.volatile規(guī)則
    volatile變量的寫贯涎,先發(fā)生于讀听哭,這保證了volatile變量的可見性,簡(jiǎn)單的理解就是塘雳,volatile變量在每次被線程訪問時(shí)欢唾,都強(qiáng)迫從主內(nèi)存中讀該變量的值,而當(dāng)該變量發(fā)生變化時(shí)粉捻,又會(huì)強(qiáng)迫將最新的值刷新到主內(nèi)存礁遣,任何時(shí)刻,不同的線程總是能夠看到該變量的最新值肩刃。
  • 4.線程啟動(dòng)規(guī)則
    線程的start()方法先于它的每一個(gè)動(dòng)作祟霍,即如果線程A在執(zhí)行線程B的start方法之前修改了共享變量的值,那么當(dāng)線程B執(zhí)行start方法時(shí)盈包,線程A對(duì)共享變量的修改對(duì)線程B可見沸呐。
  • 5.傳遞性
    A先于B ,B先于C 那么A必然先于C
  • 6.線程終止規(guī)則
    線程的所有操作先于線程的終結(jié)呢燥,Thread.join()方法的作用是等待當(dāng)前執(zhí)行的線程終止崭添。假設(shè)在線程B終止之前,修改了共享變量叛氨,線程A從線程B的join方法成功返回后呼渣,線程B對(duì)共享變量的修改將對(duì)線程A可見。
  • 7.線程中斷規(guī)則
    對(duì)線程 interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生寞埠,可以通過Thread.interrupted()方法檢測(cè)線程是否中斷屁置。
  • 8.對(duì)象終結(jié)規(guī)則
    對(duì)象的構(gòu)造函數(shù)執(zhí)行,結(jié)束先于finalize()方法

(五)volatile

volatile是Java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制仁连。volatile關(guān)鍵字有如下兩個(gè)作用:

  • 保證被volatile修飾的共享變量對(duì)所有線程總數(shù)可見的蓝角,也就是當(dāng)一個(gè)線程修改了一個(gè)被volatile修飾共享變量的值,新值總數(shù)可以被其他線程立即得知饭冬。
  • 禁止指令重排序優(yōu)化使鹅。

volatile的可見性

關(guān)于volatile的可見性作用,我們必須意識(shí)到被volatile修飾的變量對(duì)所有線程總數(shù)立即可見的昌抠,對(duì)volatile變量的所有寫操作總是能立刻反應(yīng)到其他線程中患朱,但是對(duì)于volatile變量運(yùn)算操作在多線程環(huán)境并不保證安全性。

volatile禁止重排優(yōu)化

禁止重排其實(shí)在單例模式中已經(jīng)有提現(xiàn)扰魂,就是單例模式中的雙重校驗(yàn)鎖模式麦乞。
instance = new Singleton();偽代碼如下:

memory = allocate(); //1.分配對(duì)象內(nèi)存空間
instance(memory);    //2.初始化對(duì)象
instance = memory;   //3.設(shè)置instance指向剛分配的內(nèi)存地址蕴茴,此時(shí)instance!=null

如果去掉volatile姐直,則可重排優(yōu)化為:

memory = allocate(); //1.分配對(duì)象內(nèi)存空間
instance = memory;   //3.設(shè)置instance指向剛分配的內(nèi)存地址倦淀,此時(shí)instance!=null声畏,但是對(duì)象還沒有初始化完成撞叽!
instance(memory);    //2.初始化對(duì)象

以上可以發(fā)現(xiàn),當(dāng)一條線程訪問instance不為null時(shí)插龄,由于instance實(shí)例未必已初始化完成愿棋,也就造成了線程安全問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末均牢,一起剝皮案震驚了整個(gè)濱河市糠雨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徘跪,老刑警劉巖甘邀,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異垮庐,居然都是意外死亡松邪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門哨查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逗抑,“玉大人,你說我怎么就攤上這事寒亥∮矢” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵护盈,是天一觀的道長(zhǎng)挟纱。 經(jīng)常有香客問我,道長(zhǎng)腐宋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任檀轨,我火速辦了婚禮胸竞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘参萄。我一直安慰自己卫枝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布讹挎。 她就那樣靜靜地躺著校赤,像睡著了一般吆玖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上马篮,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天沾乘,我揣著相機(jī)與錄音,去河邊找鬼浑测。 笑死翅阵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迁央。 我是一名探鬼主播掷匠,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼岖圈!你這毒婦竟也來了讹语?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蜂科,失蹤者是張志新(化名)和其女友劉穎顽决,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇摄,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擎值,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逐抑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠儿。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖厕氨,靈堂內(nèi)的尸體忽然破棺而出进每,到底是詐尸還是另有隱情,我是刑警寧澤命斧,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布田晚,位于F島的核電站,受9級(jí)特大地震影響国葬,放射性物質(zhì)發(fā)生泄漏贤徒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一汇四、第九天 我趴在偏房一處隱蔽的房頂上張望接奈。 院中可真熱鬧,春花似錦通孽、人聲如沸序宦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)互捌。三九已至潘明,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秕噪,已是汗流浹背钳降。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巢价,地道東北人牲阁。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像壤躲,于是被迫代替她去往敵國(guó)和親城菊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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