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

JVM內(nèi)存結(jié)構(gòu) VS Java內(nèi)存模型 VS Java對象模型

  • 整體方向
  • JVM內(nèi)存結(jié)構(gòu)籍铁,和Java虛擬機的運行時區(qū)域有關(guān)。
  • Java內(nèi)存模型耻煤,和Java的并發(fā)編程有關(guān)昼伴。
  • Java對象模型,和Java對象在虛擬機中表現(xiàn)形式有關(guān)溯壶。
image.png

Java對象模型

  • Java對象自身的存儲模型
  • JVM會給這個類創(chuàng)建一個instanceKlass,保存在方法區(qū)及皂,用來在JVM層表示該Java類。
  • 當我們在Java代碼中且改,使用new創(chuàng)建一個對象的時候验烧,JVM會創(chuàng)建一個instanceOopDesc對象,這個對象中包含了對象頭以及實例數(shù)據(jù)又跛。

1.內(nèi)存模型

內(nèi)存模型:在特定的操作協(xié)議下碍拆,對特定的內(nèi)存或高速緩存進行讀寫訪問的過程抽象
Java內(nèi)存模型主要關(guān)注JVM中把把變量值存儲到內(nèi)存和從內(nèi)存中取出變量值這樣的底層細節(jié)

  • 所有變量(共享的)都存儲在主內(nèi)存中,每個線程都有自己工作內(nèi)存;工作內(nèi)存中保存該線程使用到的變量的主內(nèi)存副本拷貝
  • 線程對變量的所有操作(讀慨蓝、寫)都應(yīng)該在工作內(nèi)存中完成
  • 不同線程不能相互訪問工作內(nèi)存感混,交互數(shù)據(jù)要通過主內(nèi)存
為什么需要JMM

JMM即為JAVA 內(nèi)存模型(java memory model)。因為在不同的硬件生產(chǎn)商和不同的操作系統(tǒng)下礼烈,內(nèi)存的訪問邏輯有一定的差異弧满,結(jié)果就是當你的代碼在某個系統(tǒng)環(huán)境下運行良好,并且線程安全此熬,但是換了個系統(tǒng)就出現(xiàn)各種問題庭呜。Java內(nèi)存模型,就是為了屏蔽系統(tǒng)和硬件的差異犀忱,讓一套代碼在不同平臺下能到達相同的訪問結(jié)果


image.png

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

Java內(nèi)存模型規(guī)定了一些操作來實現(xiàn)內(nèi)存間交互,JVM會保存證它們是原子的


image.png

lock:鎖定,把變量標識為線程獨占疟赊,作用于主內(nèi)存變量
unlock:解鎖,把鎖定的變量釋放峡碉,別的線程才能使用近哟,作用于主內(nèi)存變量
read:讀取,把變量值從主內(nèi)存讀取到工作內(nèi)存
load:載入鲫寄,把read讀取到的值放入工作內(nèi)存的變量副本中
use:使用吉执,把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎
assign:賦值,把從執(zhí)行引擎接收到的值賦給工作內(nèi)存里面的變量
store:存儲地来,把工作內(nèi)存中一個變量的值傳遞到主內(nèi)存中
write:寫入戳玫,把store進來的數(shù)據(jù)存放到主內(nèi)的變量中

3.內(nèi)存間交互操作的規(guī)則

  1. 如果要把一個變量從主內(nèi)存中復制到工作內(nèi)存, 就需要按順序地執(zhí) 行read和load操作 , 如果把變量從工作內(nèi)存中同步回主內(nèi)存中, 就要按順序地執(zhí)行store和write操作. 但Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行
  2. 不允許read和load、 store和write操作之一單獨出現(xiàn)
  3. 不允許一個線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中
  4. 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中
  5. 一個新的變量只能在主內(nèi)存中誕生, 不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量未斑。 即就是對一個變量實施use和store操作之前 , 必須先執(zhí)行過了assign和load操作
  6. 一個變量在同一時刻只允許一條線程對 其進行l(wèi)ock操作 , 但lock操作可以被同一條線程重復執(zhí)行多次,多7. 次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖咕宿。lock和unlock必須成對出現(xiàn)
    如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值, 在執(zhí)行引擎使用這個變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
  7. 如果一個變量事先沒有被lock操作鎖定 , 則不允許 對它執(zhí)行unlock操作 ; 也不允許去unlock一個被其他線程鎖定的變量
  8. 對一個變量執(zhí)行unlock操作之前 , 必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)

4.重排序

4.1重排序的代碼案例
/**
 * 描述:     演示重排序的現(xiàn)象 “直到達到某個條件才停止”,測試小概率事件
 */
public class OutOfOrderExecution {

    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(3);

            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    a = 1;
                    x = b;
                }
            });
            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    b = 1;
                    y = a;
                }
            });
            two.start();
            one.start();
            latch.countDown();
            one.join();
            two.join();

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


}

x,y 可以是0,1 1,0 1,1 不可能是0,0 但是輸出結(jié)果

省略...
第133601次(0,1)
第133602次(0,1)
第133603次(0,1)
第133604次(1,0)
第133605次(0,0)

Process finished with exit code 0

會出現(xiàn)x=0,y=0府阀?那是因為重排序發(fā)生了缆镣,4行代碼的執(zhí)行順序的其中一種可能:
y = a;
a = 1;
x = b;
b = 1;

重排序的例子、什么是重排序

在線程1內(nèi)部的兩行代碼的實際執(zhí)行順序和代碼在Java文件中的順序不一致试浙,代碼指令并不是嚴格按照代碼語句順序執(zhí)行的董瞻,它們的順序被改變了,這就是重排序田巴,這里被顛倒的是y=a和b=1這兩行語句钠糊。

4.2 重排序的好處:提高處理速度
  • 對比重排序前后的指令優(yōu)化
    image.png
image.png

重排序明顯提高了處理速度

4.3 重排序的3種情況
  • 編譯器優(yōu)化:
    包括JVM,JIT編譯器等
  • CPU指令重排:
    就算編譯器不發(fā)生重排壹哺,CPU也可能對指令進行重排
  • 內(nèi)存的"重排序"
    線程A的修改線程B卻看不到抄伍,引出可見性問題
    程序執(zhí)行一段代碼,寫一個普通的共享變量管宵,其可能先被寫到緩沖區(qū)然后再被寫到主內(nèi)存逝慧,此時指令完成的時間就被推遲了∽牟冢看上去像"重排序"

5.可見性

public class FieldVisibility {
     int a = 1;
     int b = 2;

    private void change() {
        a = 3;
        b = a;
    }


    private void print() {
        System.out.println("b=" + b + ";a=" + a);
    }

    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}
image.png
而b其它線程恰好可見
image.png

可見性是什么?(通俗易懂)

a b 前面加上volatile 關(guān)鍵字云稚,解決可見性問題

    volatile int a = 1;
    volatile int b = 2;
image.png
為什么會有可見性問題隧饼?
image.png

CPU有多級緩存,導致讀的數(shù)據(jù)過期:

  • 高速緩存的容量比主內(nèi)存小静陈,但是速度僅次于寄存器燕雁,所以在CPU和主內(nèi)存之間就多了Cache層;
  • 線程間的對于共享變量的可見性問題不是直接由多核引起的鲸拥,而是由多緩存引起的拐格;
  • 如果所有的核心只有一個緩存,那就不會出現(xiàn)內(nèi)存可見性問題刑赶;
    但是每個核心都會將自己需要的數(shù)據(jù)讀到獨占的緩存中捏浊,數(shù)據(jù)修改后也是寫入到緩存中,然后等待刷入到主存中撞叨,所以會導致有些核心讀取的值是一個過期的值金踪。
總結(jié):

所有的共享變量存在于主內(nèi)存中,每個線程有自己的本地內(nèi)存牵敷,而且線程讀寫共享數(shù)據(jù)也是通過工作內(nèi)存交換胡岔,但是存在延遲的情況,所以導致可見性問題的出現(xiàn)枷餐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靶瘸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怨咪,老刑警劉巖屋剑,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惊暴,居然都是意外死亡饼丘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門辽话,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肄鸽,“玉大人,你說我怎么就攤上這事油啤〉渑牵” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵益咬,是天一觀的道長逮诲。 經(jīng)常有香客問我,道長幽告,這世上最難降的妖魔是什么梅鹦? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮冗锁,結(jié)果婚禮上齐唆,老公的妹妹穿的比我還像新娘。我一直安慰自己冻河,他們只是感情好箍邮,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叨叙,像睡著了一般锭弊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上擂错,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天味滞,我揣著相機與錄音,去河邊找鬼钮呀。 笑死桃犬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的行楞。 我是一名探鬼主播攒暇,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼子房!你這毒婦竟也來了形用?” 一聲冷哼從身側(cè)響起就轧,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎田度,沒想到半個月后妒御,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡镇饺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年乎莉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奸笤。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡惋啃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出监右,到底是詐尸還是另有隱情边灭,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布健盒,位于F島的核電站绒瘦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扣癣。R本人自食惡果不足惜惰帽,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望父虑。 院中可真熱鬧该酗,春花似錦、人聲如沸频轿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽航邢。三九已至,卻和暖如春骄蝇,著一層夾襖步出監(jiān)牢的瞬間膳殷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工九火, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赚窃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓岔激,卻偏偏與公主長得像勒极,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子虑鼎,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344