Java內(nèi)存模型

JMM

計算機物理內(nèi)存模型

計算機物理內(nèi)存模型

java JMM

java內(nèi)存模型
  • Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存(Main Memory)中(此處的主內(nèi)存與 介紹物理硬件時的主內(nèi)存名字一樣等浊,兩者也可以互相類比腮郊,但此處僅是虛擬機內(nèi)存的一部 分)。每條線程還有自己的工作內(nèi)存(Working Memory筹燕,可與前面講的處理器高速緩存類 比)轧飞,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝。
  • 線程對變量的 所有操作(讀取撒踪、賦值等)都必須在工作內(nèi)存中進(jìn)行过咬,而不能直接讀寫主內(nèi)存中的變量。 不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量制妄,線程間變量值的傳遞均需要通過主 內(nèi)存來完成

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

  • 關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議掸绞,即一個變量如何從主內(nèi)存拷貝到工作內(nèi) 存、如何從工作內(nèi)存同步回主內(nèi)存之類的實現(xiàn)細(xì)節(jié)耕捞,Java內(nèi)存模型中定義了以下8種操作來 完成衔掸,虛擬機實現(xiàn)時必須保證下面提及的每一種操作都是原子的
    • lock(鎖定):作用于主內(nèi)存的變量,它把一個變量標(biāo)識為一條線程獨占的狀態(tài)俺抽。
    • unlock(解鎖):作用于主內(nèi)存的變量敞映,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放 后的變量才可以被其他線程鎖定磷斧。
    • read(讀日裨浮):作用于主內(nèi)存的變量捷犹,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi) 存中,以便隨后的load動作使用冕末。
    • load(載入):作用于工作內(nèi)存的變量萍歉,它把read操作從主內(nèi)存中得到的變量值放入工 作內(nèi)存的變量副本中。
    • use(使用):作用于工作內(nèi)存的變量档桃,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引 擎翠桦,每當(dāng)虛擬機遇到一個需要使用到變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
    • assign(賦值):作用于工作內(nèi)存的變量胳蛮,它把一個從執(zhí)行引擎接收到的值賦給工作內(nèi) 存的變量销凑,每當(dāng)虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
    • store(存儲):作用于工作內(nèi)存的變量仅炊,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存 中斗幼,以便隨后的write操作使用。
    • write(寫入):作用于主內(nèi)存的變量抚垄,它把store操作從工作內(nèi)存中得到的變量的值放入 主內(nèi)存的變量中

對于volatile型變量的特殊規(guī)則

  • 關(guān)鍵字volatile可以說是Java虛擬機提供的最輕量級的同步機制蜕窿,

    • 是保證此變量對所有線程的可 見性,這里的“可見性”是指當(dāng)一條線程修改了這個變量的值呆馁,新值對于其他線程來說是可以 立即得知的

      • 是Java里面的運算并非 原子操作桐经,導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的

        /**
          * 
          * volatile在并發(fā)環(huán)境下并非原子操作
          */
        public class VolatileTest {
        
        public static volatile int race = 0;
        
        public static void increase() {
            race++;
        }
        
        public static final int THREAD_COUNT = 20;
        
        public static void main(String[] args) {
            for (int i = 0; i < THREAD_COUNT; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        increase();
                    }
                }).start();
            }
        
            //等待所有累加線程都結(jié)束,如果還有線程在運行,主線程就讓出cpu資源
            while (Thread.activeCount() > 2) {//由于idea原因此處不能為一
                Thread.yield();
            }
            System.out.println(race);
        }
        
        /*
        public static void increase();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #2                  // Field race:I
             3: iconst_1
             4: iadd
             5: putstatic     #2                  // Field race:I
             8: return
             當(dāng)getstatic指令把race的值取到操作棧頂時浙滤,volatile關(guān)鍵字保證了race的值在此 時是正確的阴挣,但是在執(zhí)行iconst_1、iadd這些指令的時候纺腊,
             其他線程可能已經(jīng)把race的值加大 了畔咧,而在操作棧頂?shù)闹稻妥兂闪诉^期的數(shù)據(jù),所以putstatic指令執(zhí)行后就可能把較小的race值 同步回主內(nèi)存之中
        */
        }
        
        
  • 禁止指令重排序優(yōu)化

    /**
     *
     * volatile靜止指令重排序演示代碼
     */
    public class Singleton {
    
        private volatile static Singleton instance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton.getInstance();
        }
    }
    /*
    instance = new Singleton();
    這段代碼可以分為如下的三個步驟:
    memory = allocate();  // 1:分配對象的內(nèi)存空間
    ctorInstance(memory); // 2:初始化對象
    instance = memory;    // 3:設(shè)置instance指向剛分配的內(nèi)存地址
    我們知道揖膜,編輯器和處理器會進(jìn)行代碼優(yōu)化誓沸,而其中重要的一點是會將指令進(jìn)行重排序。
    上邊的代碼經(jīng)過重排序后可能會變?yōu)椋?memory = allocate();  // 1:分配對象的內(nèi)存空間
    instance = memory;    // 3:設(shè)置instance指向剛分配的內(nèi)存地址
                          // 注意:此時對象尚未初始化
    ctorInstance(memory); // 2:初始化對象
    
    代碼對應(yīng)的匯編的執(zhí)行過程
    * 0x01a3de0f:mov        $0x3375cdb0壹粟,%esi        ;……beb0cd75 33
                                                ;{oop('Singleton')}
    0x01a3de14:mov      %eax拜隧,0x150(%esi)        ;……89865001 0000
    0x01a3de1a:shr      $0x9,%esi               ;……c1ee09
    0x01a3de1d:movb $0x0趁仙,0x1104800(%esi)    ;……c6860048 100100
    0x01a3de24:lock addl$0x0洪添,(%esp)     ;……f0830424 00
                                                ;*put static instance
                                                ;-
    Singleton:getInstance@24
    
    生成匯編碼是lock addl $0x0, (%rsp), 在寫操作(put static instance)之前使用了lock前綴,鎖住了總線和對應(yīng)的地址幸撕,這樣其他的CPU寫和讀都要等待鎖的釋放薇组。
    當(dāng)寫完成后,釋放鎖坐儿,把緩存刷新到主內(nèi)存律胀。
    加了 volatile之后宋光,volatile在最后加了lock前綴,把前面的步驟鎖住了炭菌,這樣如果你前面的步驟沒做完是無法執(zhí)行最后一步刷新到內(nèi)存的罪佳,
    換句話說只要執(zhí)行到最后一步lock,必定前面的操作都完成了黑低。那么即使我們完成前面兩步或者三步了赘艳,還沒執(zhí)行最后一步lock,或者前面一步執(zhí)行了就切換線程2了克握,
    線程B在判斷的時候也會判斷實例為空蕾管,進(jìn)而繼續(xù)進(jìn)來由線程B完成后面的所有操作。當(dāng)寫完成后菩暗,釋放鎖掰曾,把緩存刷新到主內(nèi)存。
    ————————————————
    版權(quán)聲明:本文為CSDN博主「夏洛克卷」的原創(chuàng)文章停团,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議旷坦,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/zx48822821/article/details/86589753
    
    * */
    

Java與Thread

  • 線程的實現(xiàn)

    • 實現(xiàn)線程主要有3種方式:使用內(nèi)核線程實現(xiàn)佑稠、使用用戶線程實現(xiàn)和使用用戶線程加輕 量級進(jìn)程混合實現(xiàn)秒梅。

    • 內(nèi)核線程(Kernel-Level Thread,KLT)

    內(nèi)核線程
    • 用戶線程(User Thread,UT)使用用戶線程的優(yōu)勢在于不需要系統(tǒng)內(nèi)核支援,劣勢也在于沒有系統(tǒng)內(nèi)核的支援舌胶,所有 的線程操作都需要用戶程序自己處理
用戶線程
  • 使用用戶線程加輕量級進(jìn)程混合實現(xiàn)
用戶線程
  • Java線程的實現(xiàn)

    • 在目前的JDK版 本中捆蜀,操作系統(tǒng)支持怎樣的線程模型,在很大程度上決定了Java虛擬機的線程是怎樣映射 的辆琅,這點在不同的平臺上沒有辦法達(dá)成一致漱办,虛擬機規(guī)范中也并未限定Java線程需要使用哪 種線程模型來實現(xiàn)。
  • Java線程調(diào)度

  • 線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程,主要調(diào)度方式有兩種婉烟,分別是協(xié)同 式線程調(diào)度(Cooperative Threads-Scheduling)和搶占式線程調(diào)度(Preemptive ThreadsScheduling)。

  • 協(xié)同 式線程調(diào)度暇屋,線程的執(zhí)行時間由線程本身來控制似袁,線程把自己的 工作執(zhí)行完了之后,要主動通知系統(tǒng)切換到另外一個線程上咐刨。

  • 使用搶占式調(diào)度的多線程系統(tǒng)昙衅,那么每個線程將由系統(tǒng)來分配執(zhí)行時間,線程的切 換不由線程本身來決定

  • Java使用的線程調(diào)度方式就是搶 占式調(diào)度

  • 線程狀態(tài)轉(zhuǎn)換

用戶線程

線程安全與鎖優(yōu)化

  • 為了更加深入地理解線程安全定鸟,在這里我們可以不把線程安全當(dāng)做一個非真即假的二元 排他選項來看待而涉,按照線程安全的“安全程度”由強至弱來排序,我們[1]可以將Java語言中各種 操作共享的數(shù)據(jù)分為以下5類:不可變联予、絕對線程安全啼县、相對線程安全材原、線程兼容和線程對 立

  • 不可變 (Immutable)的對象一定是線程安全的,無論是對象的方法實現(xiàn)還是方法的調(diào)用者季眷,都不需 要再采取任何的線程安全保障措施

  • 在Java API中標(biāo)注自己是線程安全的類余蟹,大多數(shù) 都不是絕對的線程安全

    /**
     * 
     * 對vector線程安全的測試,通過對源碼debug測試發(fā)現(xiàn)會出現(xiàn) ArrayIndexOutOfBoundsException
     * 盡管這里使用到的Vector的get()、remove()和size()方法都是同步的子刮, 但是在多線程的環(huán)境中威酒,
     * 如果不在方法調(diào)用端做額外的同步措施的話,使用這段代碼仍然是 不安全的挺峡,因為如果另一個線程恰好在錯誤的時間里刪除了一個元素葵孤,
     * 導(dǎo)致序號i已經(jīng)不再 可用的話,再用i訪問數(shù)組就會拋出一個ArrayIndexOutOfBoundsException
     */
    public class VectorTest {
    
        private static Vector<Integer> vector = new Vector<>();
    
        public static void main(String[] args) {
            while (true) {
                for (int i = 0; i < 10; i++) {
                    vector.add(i);
                }
    
                new Thread(() -> {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.remove(i);
                    }
                }).start();
    
                new Thread(() -> {
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println(vector.get(i));
                    }
                }).start();
    
                while (Thread.activeCount() > 90) ;
            }
    
        }
    }
    

    代碼改進(jìn)

    /**
     * 
     * 改進(jìn)后debug源碼未發(fā)現(xiàn)異常情況
     */
    public class VectorTestImprove {
        private static Vector<Integer> vector = new Vector<>();
    
        public static void main(String[] args) {
            while (true) {
                for (int i = 0; i < 10; i++) {
                    vector.add(i);
                }
    
                new Thread(() -> {
                    synchronized (vector) {
                        for (int i = 0; i < vector.size(); i++) {
                            vector.remove(i);
                        }
                    }
                }).start();
    
                new Thread(() -> {
                    synchronized (vector) {
                        for (int i = 0; i < vector.size(); i++) {
                            System.out.println(vector.get(i));
                        }
                    }
                }).start();
    
                while (Thread.activeCount() > 90) ;
            }
    
        }
    }
    
    
  • 相對線程安全

  • 相對的線程安全就是我們通常意義上所講的線程安全橱赠,在Java語言中佛呻,大部分的線程安全類都屬于這種類型,例如Vector病线、HashTable吓著、 Collections的synchronizedCollection()方法包裝的集合等

  • 線程兼容

  • 線程兼容是指對象本身并不是線程安全的,但是可以通過在調(diào)用端正確地使用同步手段 來保證對象在并發(fā)環(huán)境中可以安全地使用送挑,我們平常說一個類不是線程安全的绑莺,絕大多數(shù)時 候指的是這一種情況。Java API中大部分的類都是屬于線程兼容的惕耕,如與前面的Vector和 HashTable相對應(yīng)的集合類ArrayList和HashMap等纺裁。

  • 線程對立

  • 線程對立是指無論調(diào)用端是否采取了同步措施,都無法在多線程環(huán)境中并發(fā)使用的代 碼司澎。

線程安全的實現(xiàn)方法

  • 互斥同步(Mutual Exclusion&Synchronization)是常見的一種并發(fā)正確性保障手段欺缘。同步 是指在多個線程并發(fā)訪問共享數(shù)據(jù)時,保證共享數(shù)據(jù)在同一個時刻只被一個(或者是一些挤安, 使用信號量的時候)線程使用谚殊。

  • 從處理問題的方式上說,互斥同步屬于一種悲觀的 并發(fā)策略蛤铜,總是認(rèn)為只要不去做正確的同步措施(例如加鎖)嫩絮,那就肯定會出現(xiàn)問題,無論 共享數(shù)據(jù)是否真的會出現(xiàn)競爭围肥,它都要進(jìn)行加鎖

  • 在Java中剿干,最基本的互斥同步手段就是synchronized關(guān)鍵字,我們還可以使用java.util.concurrent(下文稱J.U.C)包中的重入鎖 (ReentrantLock)來實現(xiàn)同步

  • 非阻塞同步

  • 隨著硬件指令集的發(fā)展穆刻,我們有了另外一個選擇:基于沖突檢測的 樂觀并發(fā)策略置尔,通俗地說,就是先進(jìn)行操作氢伟,如果沒有其他線程爭用共享數(shù)據(jù)榜轿,那操作就成 功了幽歼;如果共享數(shù)據(jù)有爭用,產(chǎn)生了沖突差导,那就再采取其他的補償措施(最常見的補償措施 就是不斷地重試试躏,直到成功為止),這種樂觀的并發(fā)策略的許多實現(xiàn)都不需要把線程掛起设褐, 因此這種同步操作稱為非阻塞同步(Non-Blocking Synchronization)颠蕴。

鎖優(yōu)化

  • 自旋鎖與自適應(yīng)自旋

  • 互斥同步對性能最大的影響是阻塞的實現(xiàn),掛起 線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成助析,這些操作給系統(tǒng)的并發(fā)性能帶來了很大的 壓力犀被。同時,虛擬機的開發(fā)團隊也注意到在許多應(yīng)用上外冀,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短 的一段時間寡键,為了這段時間去掛起和恢復(fù)線程并不值得。如果物理機器有一個以上的處理 器雪隧,能讓兩個或以上的線程同時并行執(zhí)行西轩,我們就可以讓后面請求鎖的那個線程“稍等一 下”,但不放棄處理器的執(zhí)行時間脑沿,看看持有鎖的線程是否很快就會釋放鎖藕畔。為了讓線程等 待,我們只需讓線程執(zhí)行一個忙循環(huán)(自旋)庄拇,這項技術(shù)就是所謂的自旋鎖注服,

  • 自旋等待本身雖然避免了線程切換的開銷,但它是要占用處理器時間的措近, 因此溶弟,如果鎖被占用的時間很短,自旋等待的效果就會非常好瞭郑,反之辜御,如果鎖被占用的時間 很長,那么自旋的線程只會白白消耗處理器資源凰浮,而不會做任何有用的工作我抠,反而會帶來性 能上的浪費。因此袜茧,自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數(shù)仍然 沒有成功獲得鎖瓣窄,就應(yīng)當(dāng)使用傳統(tǒng)的方式去掛起線程了

  • 自適應(yīng)意味著自旋的時間不再固定了笛厦,而是由前 一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俺夕,隨后出現(xiàn)的幾起案子裳凸,更是在濱河造成了極大的恐慌贱鄙,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姨谷,死亡現(xiàn)場離奇詭異逗宁,居然都是意外死亡,警方通過查閱死者的電腦和手機梦湘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門瞎颗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捌议,你說我怎么就攤上這事哼拔。” “怎么了瓣颅?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵倦逐,是天一觀的道長。 經(jīng)常有香客問我宫补,道長檬姥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任粉怕,我火速辦了婚禮健民,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斋荞。我一直安慰自己荞雏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布平酿。 她就那樣靜靜地躺著凤优,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜈彼。 梳的紋絲不亂的頭發(fā)上筑辨,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音幸逆,去河邊找鬼棍辕。 笑死,一個胖子當(dāng)著我的面吹牛还绘,可吹牛的內(nèi)容都是我干的楚昭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼拍顷,長吁一口氣:“原來是場噩夢啊……” “哼抚太!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤尿贫,失蹤者是張志新(化名)和其女友劉穎电媳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庆亡,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匾乓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了又谋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拼缝。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搂根,靈堂內(nèi)的尸體忽然破棺而出珍促,到底是詐尸還是另有隱情,我是刑警寧澤剩愧,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布猪叙,位于F島的核電站,受9級特大地震影響仁卷,放射性物質(zhì)發(fā)生泄漏穴翩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一锦积、第九天 我趴在偏房一處隱蔽的房頂上張望芒帕。 院中可真熱鬧,春花似錦丰介、人聲如沸背蟆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽带膀。三九已至,卻和暖如春橙垢,著一層夾襖步出監(jiān)牢的瞬間垛叨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工柜某, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗽元,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓喂击,卻偏偏與公主長得像剂癌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翰绊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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