Java學習內(nèi)存模型以及線程安全的可見性問題(八)

上次線程池已經(jīng)說過了,從今天開始一起了解下JVM內(nèi)存模型詳解。

(一)容易誤解的部分

老鐵很容易把JAVA的內(nèi)存區(qū)域、JAVA的內(nèi)存模型,GC分代回收的老年代和新生代也容易搞混富弦,繞進去繞不出來。學習多線程之前一定要搞明白這些問題氛驮,可能在你的內(nèi)心一直認為多線程就是一個工具腕柜,所有的底層都是C++來寫的,沒辦法去看,為什么要有java盏缤,java其實就是屏蔽了底層的復雜性砰蠢。

  • ① GC內(nèi)存區(qū)域

堆的概念,老年代唉铜,新生代台舱,Eden,S0潭流,S1

  • ② JAVA的內(nèi)存區(qū)域

JVM運行時的區(qū)域:java編譯生成class竞惋,線程共享部分(方法區(qū),堆內(nèi)存)灰嫉,線程獨占部分(虛擬機棧拆宛,本地方法棧,程序計數(shù)器)

  • ③ JAVA的內(nèi)存模型(概念)

針對多核多CPU讼撒,多線程而制定的一套規(guī)范規(guī)則浑厚,不是一種開發(fā)技術(shù)。

(二)多線程中的問題

  1. 所見非所得(你看到的并不是所想的)根盒、
  2. 無法肉眼去檢測程序的準確性(多線程下钳幅,完全看不出來正常不正常)。
  3. 不同的運行平臺有不同的表現(xiàn)郑象。
  4. 錯誤很難重現(xiàn)贡这。

(三)工作內(nèi)存和主內(nèi)存

  • ① 主內(nèi)存

創(chuàng)建一個對象在堆里面茬末,也可以稱之為主內(nèi)存厂榛,不僅僅是在堆,存在一個對象X丽惭,就存在主內(nèi)存

  • ② 工作內(nèi)存

線程運行在工作內(nèi)存击奶, 虛擬機棧,程序計數(shù)器责掏,CPU柜砾,高速緩存。

工作內(nèi)存和主內(nèi)存只是一個邏輯上的劃分换衬,概念上的東西痰驱。

  • ③ 奇妙的現(xiàn)象

主內(nèi)存的flag傳輸?shù)焦ぷ鲀?nèi)存flag的時候,存在CPU緩存的情況瞳浦,CPU緩存可能導致非常短的時間內(nèi)不一致担映,本身CPU廠家底層是要做一致處理的,但是存在短時間內(nèi)的不一致叫潦。

(四)指令重排

  • ① 介紹

Java語言規(guī)范JVM線程內(nèi)部維持順序或語義蝇完,即只要程序的最終結(jié)果與它順序化情況的結(jié)果相等,那么指令的執(zhí)行順序可以與代碼邏輯順序不一致,這個過程就叫做指令的重排序短蜕。

  • ② 意義

使指令更加符合CPU的執(zhí)行特性氢架,最大限度的發(fā)揮機器的性能,提高程序的執(zhí)行效率朋魔。

重排序岖研,只能保證單個線程的,如果是多線程的話警检,就沒有爆發(fā)保證重排序缎玫。

// 線程1 
a = d; b = 2
// 線程2 
c = a; d =3

//重排序后
//線程1 
b = 2 ; a =d;
//線程2
d = 3 ; c =a;

編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時解滓,會遵守數(shù)據(jù)依賴性赃磨,編譯器和處理器不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序。

(五) 如何不進行指令排序

  • ① 介紹

The Java volatile keyword is used to mark a Java variable as "being stored in main memory". More precisely that means, that every read of a volatile variable will be read from the computer's main memory, and not from the CPU cache, and that every write to a volatile variable will be written to main memory, and not just to the CPU cache洼裤。 CPU不緩存邻辉。

  • ② 實例
public class VisibilityDemo2 {
    // 狀態(tài)標識 (不用緩存)
    private volatile boolean flag = true;

    // 源碼 -> 字節(jié)碼class
    // JVM 轉(zhuǎn)化為 操作系統(tǒng)能夠執(zhí)行的代碼 (JIT Just In Time Compiler 編譯器 )(JVM  --  client   , --server)
    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo2 demo1 = new VisibilityDemo2();
        new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (demo1.flag) {
                    i++;
                }
                System.out.println(i);
            }
        }).start();

        TimeUnit.SECONDS.sleep(2);
        // 設(shè)置is為false腮鞍,使上面的線程結(jié)束while循環(huán)
        demo1.flag = false;
        System.out.println("被置為false了.");
    }
}

不添加volatile 值骇,就不會打印i的值。

(六) 內(nèi)存模型

  • ① 介紹

內(nèi)存模型描述程序的可能行為移国。JAVA編程語言內(nèi)存模型通過檢查執(zhí)行跟蹤中的每個讀操作吱瘩,并根據(jù)某些規(guī)則檢查該操作觀察到的寫操作是否有效來工作。

只要程序的所有執(zhí)行產(chǎn)生的結(jié)果都可以由內(nèi)存模型預測迹缀,具體的實現(xiàn)者任意實現(xiàn)使碾,包括操作的重新排序和刪除不必要的同步。

內(nèi)存模型決定了在程序的每個點上可以讀取什么值祝懂。

  • ② 共享變量描述

可以在線程之間共享的內(nèi)存稱為共享內(nèi)存或堆內(nèi)存票摇。所有實例字段,靜態(tài)字段和數(shù)組元素都存儲在堆內(nèi)存中砚蓬。如果至少有一個訪問是寫的矢门,那么對同一個變量的兩次訪問(讀或?qū)懀┦菦_突的。線程1修改過共享變量后灰蛙,將共享變量刷到主內(nèi)存祟剔,然后,線程2從主內(nèi)存讀取該共享變量摩梧,將該共享變量載入到工作內(nèi)存中物延。

  • ③ 線程操作的定義
  1. write要寫的變量以及要寫的值。
  2. read 要讀的變量以及可見的寫入值(由此障本,我們可以確定可見的值)教届。
  3. lock 要鎖定的管程响鹃。
  4. unlock 要解鎖的管程。
  5. 外部操作(socket等等)案训。
  6. 啟動和終止买置。

如果一個程序沒有數(shù)據(jù)競爭,那么程序的所有執(zhí)行看起來都是順序一致的强霎。這是重排序必須要遵守的規(guī)則忿项。

(七)對于同步規(guī)則的定義

  • ① 對于監(jiān)視器m的解鎖與所有后續(xù)操作對于m的加鎖同步

synchronized 在同步關(guān)鍵字,在內(nèi)存中都明確定義了城舞,保持可見轩触,及時反饋給主內(nèi)存中。一環(huán)扣一環(huán)家夺,想可見脱柱,必須反饋到主內(nèi)存。既有同步的語義拉馋,還有保持可見性的功能榨为。


import java.util.concurrent.TimeUnit;

public class VisibilityDemo1 {
    // 狀態(tài)標識
    private static boolean is = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (VisibilityDemo1.is) {
                    synchronized (this) {
                        i++;
                    }
                }
                System.out.println(i);
            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 設(shè)置is為false,使上面的線程結(jié)束while循環(huán)
        VisibilityDemo1.is = false;
        System.out.println("被置為false了.");
    }
}

  • ② 對volatile 變量v的寫入煌茴,與所有其他線程后續(xù)對 v 的讀同步

變量標識了volatile 随闺,后面不管哪個線程來讀,都是同步的蔓腐,都是可見的矩乐。

public class VisibilityDemo {
    private volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo1 = new VisibilityDemo();
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                // class ->  運行時jit編譯  -> 匯編指令 -> 重排序
                while (demo1.flag) { // 指令重排序
                    i++;
                }
                System.out.println(i);
            }
        });
        thread1.start();

        TimeUnit.SECONDS.sleep(2);
        // 設(shè)置is為false,使上面的線程結(jié)束while循環(huán)
        demo1.flag = false;
        System.out.println("被置為false了.");
    }
}

  • ③ 啟動線程的操作與線程中的第一個操作同步
  • ④ 對于每個屬性寫入默認值(0回论,false散罕,null)與 每個線程對其操作的同步

  • ⑤ 線程T1的最后操作與線程T2發(fā)現(xiàn)線程T1已經(jīng)結(jié)束同步(isAlive,join可以判斷線程是否終結(jié))

  • ⑥ 如果線程T1終端了T2,那么線程T1的中斷操作與其他所有線程發(fā)現(xiàn)T2倍中斷了同步透葛,通過拋出InterruptedException異常笨使,或者調(diào)用Thread.interrupted 或者 Thread.isInterrupted。

(八)Happyens-before先行發(fā)生原則

  • ① 介紹

強調(diào)兩個有沖突的動作之間的順序僚害,以及定義數(shù)據(jù)征用的發(fā)生時機。

  • ② 原則
  1. 同一個線程里面對數(shù)據(jù)做了變動繁调,后面的動作可以及時的看到萨蚕,其實還是可見性。
  2. 某個monitor上的unlock動作 happens-before 同一個monitor上后續(xù)的lock動作蹄胰。
  3. 對某個volatile 字段的寫操作 happens-before 每個后續(xù)對該 volatile 字段的讀操作岳遥。
  4. 在某個線程對象上調(diào)用start() 方法 happens-before 該啟動了的線程中的任意動作。
  5. 某個線程中的所有動作 happens-before 任意其他線程成功從該線程對象上的join() 中返回裕寨。
  6. 如果某個動作 a 在happens-before 動作 b浩蓉,b 在happens-before 動作 c派继,則 a happens-before c。

(九) final 在JMM中的處理

  • ① final在該對象的構(gòu)造函數(shù)中設(shè)置對象的字段捻艳,當線程看到該對象時驾窟,將始終看到該對象的final字段的正確構(gòu)造版本。
  • ② 如果在構(gòu)造函數(shù)中設(shè)置字段后發(fā)生讀取认轨,則會看到該final字段分配的值绅络,否則它將看到默認值。

  • ③ 讀取該共享對象的final成員變量之前嘁字,先要讀取共享對象恩急。

  • ④ 通常static final 是不可以修改的字段。然而System.in, System.out 和 System.err 是static final 字段纪蜒,遺留原因衷恭,必須允許通過set方法改變,這些字段稱為寫保護纯续,以區(qū)別于普通的final字段匾荆。

PS:使用了volatile,unlock和lock的時候杆烁,就可以保證代碼不進行重排序牙丽。內(nèi)存模型java進階的一個核心點,這個理解了兔魂,其實比寫多少年的業(yè)務代碼要重要很多烤芦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市析校,隨后出現(xiàn)的幾起案子构罗,更是在濱河造成了極大的恐慌,老刑警劉巖智玻,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂唧,死亡現(xiàn)場離奇詭異,居然都是意外死亡吊奢,警方通過查閱死者的電腦和手機盖彭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來页滚,“玉大人召边,你說我怎么就攤上這事」郏” “怎么了隧熙?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幻林。 經(jīng)常有香客問我贞盯,道長音念,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任躏敢,我火速辦了婚禮闷愤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘父丰。我一直安慰自己肝谭,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布蛾扇。 她就那樣靜靜地躺著攘烛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镀首。 梳的紋絲不亂的頭發(fā)上坟漱,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音更哄,去河邊找鬼芋齿。 笑死,一個胖子當著我的面吹牛成翩,可吹牛的內(nèi)容都是我干的觅捆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼麻敌,長吁一口氣:“原來是場噩夢啊……” “哼栅炒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起术羔,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赢赊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后级历,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體释移,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年寥殖,在試婚紗的時候發(fā)現(xiàn)自己被綠了玩讳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡扛禽,死狀恐怖锋边,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情编曼,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布剩辟,位于F島的核電站掐场,受9級特大地震影響往扔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熊户,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一萍膛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚷堡,春花似錦蝗罗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至北苟,卻和暖如春桩匪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背友鼻。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工傻昙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彩扔。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓妆档,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虫碉。 傳聞我的和親對象是個殘疾皇子贾惦,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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