Java并發(fā)基礎(chǔ)之內(nèi)存模型

并發(fā)三問題

  • 重排序
  • 內(nèi)存可見性
  • 原子性

1. 重排序

public class Test {

    private static int x = 0, y = 0;
    private static int a = 0, b =0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            CountDownLatch latch = new CountDownLatch(1);

            Thread one = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                a = 1;
                x = b;
            });

            Thread other = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                b = 1;
                y = a;
            });
            one.start();other.start();
            latch.countDown();
            one.join();other.join();

            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

觀察代碼可以發(fā)現(xiàn)甜橱,如果沒有意外情況發(fā)生的話佃牛,在上下兩個線程中略板,出現(xiàn)的結(jié)果應該下面三種情況

x= 0 ,y = 1; </p>
x= 1 ,y = 1; </p>
x= 1 ,y = 0;

但是在實際運行過程中洼哎,卻最終有概率出現(xiàn) x=0,y=0的情況。這種情況發(fā)生的原因就是出現(xiàn)了重排序让簿。

重排序由一下幾種機制引起:</p>

  1. 編譯器優(yōu)化:對于沒有數(shù)據(jù)依賴關(guān)系的操作,編譯器在編譯的過程中會進行一定程度的重排秀睛。</p>
    可以看到線程1中的代碼尔当,編譯器是可以將a=1和x=b換一下順序的,因為它們之間沒有數(shù)據(jù)依賴關(guān)系,同理,線程2也一樣凭涂,那就不難得到x==y==0的結(jié)果了。</p>
  2. 指令重排序:CPU優(yōu)化行為畜号,也是會對不存在數(shù)據(jù)依賴關(guān)系的指令進行一定程度的重排</p>
    這個和編譯器優(yōu)化差不多,就算編譯器不發(fā)生重排缠黍,CPU也可以對指令進行重排弄兜。</p>
  3. 內(nèi)存系統(tǒng)重排序:內(nèi)存系統(tǒng)沒有重排序药蜻,但是由于緩存的存在瓷式,使得程序整體上會表現(xiàn)出亂序的行為。</p>
    假設不發(fā)生編譯器重排和指令重排语泽,線程1修改了a的值贸典,但是修改以后,a的值可能還沒寫回到主內(nèi)存中踱卵,那么線程2得到a==0就是很自然的事了廊驼。同理,線程2對于b的賦值操作也可能沒有及時刷新到主存中惋砂。

2.內(nèi)存可見性

線程間的對于共享變量的可見性問題不是直接由多核引起的妒挎,而是由多緩存引起的。如果每個核心共享同一個緩存西饵,那么也就不存在內(nèi)存可見性問題了酝掩。 </p>
現(xiàn)代多核CPU中每個核心擁有自己的一級緩存或一級緩存加上二級緩存等,問題就發(fā)生在每個核心的獨占緩存上眷柔。每個核心都將會自己需要的數(shù)據(jù)讀到獨占緩存中期虾,數(shù)據(jù)修改后也是寫入到緩存中,然后等待刷入到主存中驯嘱。所以會導致有些核心讀取的值是一個過期的值镶苞。</p>
在JMM中,抽象了主內(nèi)存和本地內(nèi)存的概念鞠评。</p>
所有的共享變量存在于主內(nèi)存中茂蚓,每個線程有自己的本地內(nèi)存,線程讀寫共享數(shù)據(jù)也是通過本地內(nèi)存交換,所以可見性問題依然存在煌贴。這里說的本地緩存并不是真的是一塊給每個線程分配的內(nèi)存御板,而是JMM的一個抽象,是對于寄存器牛郑、一級緩存怠肋、二級緩存等的抽象。

3.原子性

對于long和double淹朋,它們的值都需要占用64位的內(nèi)存空間笙各,Java編程語言規(guī)范中提到,對于64位的值的寫入础芍,可以分為兩個32位的操作進行寫入杈抢。本來一個整體的賦值操作,將被拆分為低32位賦值和高32位賦值兩個操作仑性,中間如果發(fā)生了其他線程對于這個值的讀操作惶楼,必然會讀到一個奇怪的值。</p>
這個時候我們需要使用volatile關(guān)鍵字進行控制了诊杆,JMM規(guī)定了對于volatile long 和volatile double歼捐,JVM需要保證寫入操作的原子性。</p>
另外晨汹,對于引用的讀寫操作始終是原子的豹储,不管是32位的機器還是64位的機器。</p>
Java編程規(guī)范同樣提到淘这,鼓勵JVM的開發(fā)者能保證64位值操作的原子性剥扣,也鼓勵使用盡量使用volatile或使用正確的同步方式。關(guān)鍵詞是“鼓勵”铝穷。</p>
在64位JVM中钠怯,不加volatile也是可以的,同樣能保證對于long和double寫操作的原子性曙聂。

Java對于并發(fā)的規(guī)范約束

Synchronization Order

  • 對于監(jiān)視器m的解鎖與所有后續(xù)操作對于m的加鎖同步
  • 對于volatile變量v的寫入晦炊,與所有其他線程后續(xù)對v的讀同步
  • 啟動線程的操作與線程職工的第一個操作同步
  • 對于每個屬性寫入默認值(0,false,null)與每個線程對其進行的操作同步。

Happens-before Order

兩個操作可以用Happens-before來確定它們的執(zhí)行順序筹陵,如果一個操作happens-before于另一個操作刽锤,那么我們說第一個操作對于第二個操作是可見的。</p>
如果我們分別有操作X和操作Y朦佩,我們寫成hb(x,y)并思,來表示 x happens-before y 。</p>

  • 如果操作x和操作y是同一個線程的兩個操作语稠,并且在代碼上操作x先于操作y出現(xiàn)宋彼,那么有hb(x,y)弄砍。
  • 對象構(gòu)造方法的最后一行指令happens-before于finalize()方法的第一行指令。
  • 對于操作x與隨后的操作y構(gòu)成同步输涕,那么hb(x,y)
  • hb(x,y)和hb(y,z)音婶,那么可以推斷出hb(x,z)

這里需要說明的是:hb(x,y),并不是說x操作一定要在y操作之前被執(zhí)行,而是說x的執(zhí)行結(jié)果對于y是可見的莱坎,只要滿足可見性衣式,發(fā)生了重排序也是可以的。

synchronized關(guān)鍵字

一個線程獲取到鎖以后才能進入synchronized控制的代碼塊檐什,一旦進入代碼塊碴卧,首先,該線程對于共享變量的緩存就會失效乃正,因此synchornized代碼塊中對于共享變量的讀取需要從主內(nèi)存中重新獲取住册,也就能獲取到最新的值。</p>
退出代碼塊的時候瓮具,會將該線程寫緩沖區(qū)的數(shù)據(jù)刷到主內(nèi)存中荧飞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市名党,隨后出現(xiàn)的幾起案子叹阔,更是在濱河造成了極大的恐慌,老刑警劉巖兑巾,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件条获,死亡現(xiàn)場離奇詭異忠荞,居然都是意外死亡蒋歌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門委煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堂油,“玉大人,你說我怎么就攤上這事碧绞「颍” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵讥邻,是天一觀的道長迫靖。 經(jīng)常有香客問我,道長兴使,這世上最難降的妖魔是什么系宜? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮发魄,結(jié)果婚禮上盹牧,老公的妹妹穿的比我還像新娘俩垃。我一直安慰自己,他們只是感情好汰寓,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布口柳。 她就那樣靜靜地躺著,像睡著了一般有滑。 火紅的嫁衣襯著肌膚如雪跃闹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天毛好,我揣著相機與錄音辣卒,去河邊找鬼。 笑死睛榄,一個胖子當著我的面吹牛荣茫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播场靴,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼啡莉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旨剥?” 一聲冷哼從身側(cè)響起咧欣,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轨帜,沒想到半個月后魄咕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蚌父,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年哮兰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苟弛。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡喝滞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膏秫,到底是詐尸還是另有隱情右遭,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布缤削,位于F島的核電站窘哈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亭敢。R本人自食惡果不足惜滚婉,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吨拗。 院中可真熱鬧满哪,春花似錦婿斥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至像鸡,卻和暖如春活鹰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背只估。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工志群, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛔钙。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓锌云,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吁脱。 傳聞我的和親對象是個殘疾皇子桑涎,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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