volatile

JMM

JMM(Java內(nèi)存模型逸月,Java Memory Model)本身是一種抽象的概念矩屁,并不真實(shí)存在抡柿,它描述的是一組規(guī)則或規(guī)范舔琅,通過(guò)規(guī)范定制了程序中的各個(gè)變量的訪問(wèn)方式。

JMM關(guān)于同步的規(guī)定:

  1. 線程解鎖前沙绝,必須把共享變量的值刷新回主內(nèi)存搏明。
  2. 線程加鎖前鼠锈,必須讀取主內(nèi)存的最新值到自己的工作內(nèi)存。
  3. 加鎖解鎖必須是同一個(gè)鎖星著。

特點(diǎn):

  1. 原子性:即一個(gè)操作或多個(gè)操作在執(zhí)行的過(guò)程中购笆,要成功都成功,要失敗都失敗虚循。
  2. 可見性:多個(gè)線程訪問(wèn)同一個(gè)變量時(shí)同欠,當(dāng)一個(gè)線程修改該變量時(shí),其他線程可見横缔。
  3. 有序性:保證程序運(yùn)行的順序是代碼的順序铺遂。 在java 內(nèi)存模型中,為了效率茎刚,是允許編譯器和處理器對(duì)指令進(jìn)行重排序的襟锐,對(duì)單線程運(yùn)行不會(huì)影響,但是會(huì)影響多線程運(yùn)行結(jié)果膛锭。
happens-before

了解有序性后需要了解一下happens-before原則:
定義:

  1. 如果一個(gè)操作happens-before另一個(gè)操作粮坞,那么第一個(gè)操作的執(zhí)行結(jié)果對(duì)第二個(gè)操作執(zhí)行結(jié)果可見,而且第一個(gè)操作順序在第二個(gè)之前
  2. 如果兩個(gè)操作存在happens-before關(guān)系初狰,并不意味著一定按照happens-before順序執(zhí)行莫杈。如果重排序后的運(yùn)行的值和happens-before運(yùn)行結(jié)果一樣,那么這種重排序并不違法

規(guī)則(來(lái)源 深入理解 Java 虛擬機(jī))

  1. 程序次序規(guī)則:一個(gè)線程內(nèi)奢入,按照代碼順序筝闹,書寫在前面的操作,happens-before 于書寫在后面的操作腥光。
  2. 鎖定規(guī)則:一個(gè) unLock 操作关顷,happens-before 于后面對(duì)同一個(gè)鎖的 lock 操作。
  3. volatile 變量規(guī)則:對(duì)一個(gè)變量的寫操作武福,happens-before 于后面對(duì)這個(gè)變量的讀操作解寝。
  4. 傳遞規(guī)則:如果操作 A happens-before 操作 B,而操作 B happens-before 操作C艘儒,則可以得出,操作 A happens-before 操作C
  5. 線程啟動(dòng)規(guī)則:Thread 對(duì)象的 start 方法夫偶,happens-before 此線程的每個(gè)一個(gè)動(dòng)作界睁。
  6. 線程中斷規(guī)則:對(duì)線程 interrupt 方法的調(diào)用,happens-before 被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生兵拢。
  7. 線程終結(jié)規(guī)則:線程中所有的操作翻斟,都 happens-before 線程的終止檢測(cè),我們可以通過(guò)Thread.join() 方法結(jié)束说铃、Thread.isAlive() 的返回值手段访惜,檢測(cè)到線程已經(jīng)終止執(zhí)行嘹履。
  8. 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成,happens-before 它的 finalize() 方法的開始

JMM內(nèi)存模型:


JMM內(nèi)存模型.png
數(shù)據(jù)一致性的問(wèn)題(可見性問(wèn)題)

從上圖可以看到债热,計(jì)算機(jī)在執(zhí)行的過(guò)程中砾嫉,由于每條指令都是在CPU中運(yùn)行,這樣就會(huì)涉及到去主存中讀取數(shù)據(jù)窒篱,但是主存的運(yùn)行速度沒有CPU運(yùn)行速度快焕刮,所以有了CPU高速緩存,CPU高速緩存屬于某個(gè)CPU獨(dú)有墙杯,只與運(yùn)行在該CPU的線程有關(guān)配并。這種情形解決了效率的問(wèn)題,但是也帶來(lái)了新的問(wèn)題高镐,即數(shù)據(jù)一致性溉旋,當(dāng)CPU第一次從主存中獲取后,會(huì)將信息放入到CPU高速緩存中嫉髓,這是有其他線程修改了主存的信息观腊,這是就會(huì)引起CPU緩存的信息和主存的信息不一致。

volatile

volatile 可以理解為輕量級(jí)的synchronized岩喷。在多線程的開發(fā)過(guò)程中恕沫,保證了內(nèi)存可見性及禁止重排序。

特點(diǎn)

  1. 保證可見性
  2. 不保證原子性
  3. 禁止指令重排序
保證可見性
class Demo {

    //    private volatile int number = 0;  //1
    private int number = 0;   //2


    public void add() {
        this.number = 100;
    }

    public int getNumber() {
        return number;
    }

    public Demo setNumber(int number) {
        this.number = number;
        return this;
    }

}

/**
 * 兩種運(yùn)行結(jié)果
 * 當(dāng)執(zhí)行1時(shí)候纱意,線程1修改完后婶溯,立刻輸出  內(nèi)存可見
 * 當(dāng)執(zhí)行2時(shí)候,線程1修改完后偷霉,程序死循環(huán)
 */
public class test {
    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(() -> {
            System.err.println(Thread.currentThread().getName() + "開始 執(zhí)行");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            demo.add();
            System.err.println(Thread.currentThread().getName() + "已經(jīng)執(zhí)行完,當(dāng)前number = " + demo.getNumber());
        }, "線程1").start();

        while (demo.getNumber() == 0) {

        }
        System.err.println("內(nèi)存可見");

    }
}
不保證原子性

volatile 不能保證復(fù)合操作的原子性迄委。如下:

//創(chuàng)建初始值a = 0
    private static volatile Integer a = 0;

    //a ++;
    public static void add() {
        a++;
    }

    public static void main(String[] args) throws InterruptedException {
        // 模擬一共一百個(gè)線程同時(shí)對(duì)a進(jìn)行a++操作,如果是原子的操作类少,則最終結(jié)果為100叙身;
        CountDownLatch countDownLatch = new CountDownLatch(100);
        //創(chuàng)建一個(gè)線程池(快速創(chuàng)建,但是在開發(fā)過(guò)程中不要這么寫硫狞,可能會(huì)內(nèi)存泄露信轿,后面會(huì)單獨(dú)講解)
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executor.execute(() -> {
                add();
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        //嘗試執(zhí)行了5次
        System.err.println(a); //結(jié)果:100、99残吩、92财忽、93、100
        // 關(guān)閉線程池
        executor.shutdown();
    }
為什么volatile無(wú)法保證復(fù)合操作的原子性泣侮?
public class Demo2 {
    private volatile int a = 0;

    public void add() {
        a++;
    }
}

public class com.hhb.concurrency.atguigu.thread.Demo2 {
  public com.hhb.concurrency.atguigu.thread.Demo2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field b:I
       9: aload_0
      10: iconst_0
      11: putfield      #3                  // Field a:I
      14: return

  public void add(); //對(duì)應(yīng)上面add()方法
    Code:
       0: aload_0                       //從局部變量0中裝載引用類型值
       1: dup                             //復(fù)制棧頂部一個(gè)字長(zhǎng)內(nèi)容
       2: getfield      #3                 //1即彪、先獲取a的值
       5: iconst_1
       6: iadd                                // 2、真正的對(duì)a++
       7: putfield      #3                  // 3活尊、 最后在賦值給a
      10: return
}

通過(guò)上面的代碼可以看到隶校,a++的操作在java中看起來(lái)是一個(gè)操作漏益,但是在執(zhí)行的時(shí)候,被分為了下面三個(gè)操作深胳;那么問(wèn)題就出現(xiàn)在下面那三個(gè)步驟中绰疤,當(dāng)A線程獲取到a值,并對(duì)a進(jìn)行++操作完后稠屠,正要執(zhí)行(7)的時(shí)候峦睡,B線程開始獲取a的值,此時(shí)A線程中a的值還沒有刷新回主內(nèi)存权埠,所以B在獲取到的a的值還是0榨了,然后繼續(xù)執(zhí)行a+1的操作,并刷新a在主內(nèi)存中的值攘蔽,a=1龙屉,然后A線程在執(zhí)行,刷新主內(nèi)存满俗,a=1转捕,此時(shí)兩個(gè)線程分別都進(jìn)行的a++,目標(biāo)應(yīng)該是2唆垃,但是實(shí)際結(jié)果是1.

       2: getfield      #3                 //1五芝、先獲取a的值
       5: iconst_1
       6: iadd                                // 2、真正的對(duì)a++
       7: putfield      #3                  // 3辕万、 最后在賦值給a
禁止重排序
  • 編譯器重排序:編譯器在不影響單線程運(yùn)行結(jié)果的前提之前枢步,可以重新安排語(yǔ)句的執(zhí)行順序
  • 處理器重排序:不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)的機(jī)器碼的執(zhí)行順序渐尿。
volatile如何做到的禁止重排序醉途?

內(nèi)存屏障 ( Memory Barrier)
由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化。如果在指令間插入一條Memory Barrier則會(huì)告訴編譯器和CPU砖茸,不管什么指令都不能和這條Memory Barrier指令重排序隘擎,也就是說(shuō)通過(guò)插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化。內(nèi)存屏障另外一個(gè)作用就是強(qiáng)制刷新出各種CPU的緩存數(shù)據(jù)凉夯,因此在CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本货葬。

策略:

  1. 在每個(gè)volatile寫之前,插入一個(gè)StoreStore屏障
  2. 在每個(gè)volatile寫之后劲够,插入一個(gè)StoreLoad屏障
  3. 在每個(gè)volatile讀之前宝惰,插入一個(gè)LoadLoad屏障
  4. 在每個(gè)volatile讀之后,插入一個(gè)LoadStore屏障

原因:

  1. StoreStore屏障:保證在volatile寫之前再沧,其前面的所有的普通寫操作,都已經(jīng)刷新到主存中
  2. StoreLoad屏障:避免volatile寫和后面發(fā)發(fā)生的volatile 讀/寫操作重排序
  3. LoadLoad屏障:禁止處理器把上面的volatile讀與下面的普通讀重排序
  4. LoadStore屏障:禁止處理器把上面的volatile讀與下面普通寫重排序

禁止重排序最著名的例子:雙重檢查的單例模式

/**
 * 創(chuàng)建對(duì)象的過(guò)程:
 * 1尊残、 memory = allocate()  分配對(duì)象內(nèi)存空間
 * 2炒瘸、ctorInstance()  初始化對(duì)象
 * 3淤堵、instance = memory 設(shè)置instance指向剛才的分配的內(nèi)存
 * <p>
 * 不安全的原因:
 * cpu和jvm優(yōu)化,發(fā)生了指令重排序
 * <p>
 * 上面的過(guò)程變成了
 * 1顷扩、 memory = allocate()  分配對(duì)象內(nèi)存空間
 * 2拐邪、instance = memory 設(shè)置instance指向剛才的分配的內(nèi)存
 * 3、ctorInstance()  初始化對(duì)象
 * <p>
 * <p>
 * 假設(shè)現(xiàn)在有兩個(gè)線程 A隘截、B
 * B線程執(zhí)行到了4扎阶,但是執(zhí)行到上面創(chuàng)建對(duì)象的第二步,還沒有初始化時(shí)
 * A線程指向到了1步驟婶芭,就會(huì)直接返回
 */

//private volatile static SingletonExample5 instance;

private static SingletonExample5 instance;

private SingletonExample5() {

}

public static SingletonExample5 getInstance() {
    if (instance == null) {  // 1
        synchronized (SingletonExample5.class) { // 2
            if (instance == null) { // 3
                instance = new SingletonExample5(); // 4
            }
        }
    }
    return instance; //5
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末东臀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子犀农,更是在濱河造成了極大的恐慌惰赋,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呵哨,死亡現(xiàn)場(chǎng)離奇詭異赁濒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)孟害,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門拒炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挨务,你說(shuō)我怎么就攤上這事击你。” “怎么了耘子?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵果漾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谷誓,道長(zhǎng)绒障,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任捍歪,我火速辦了婚禮户辱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糙臼。我一直安慰自己庐镐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布变逃。 她就那樣靜靜地躺著必逆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上名眉,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天粟矿,我揣著相機(jī)與錄音,去河邊找鬼损拢。 笑死陌粹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的福压。 我是一名探鬼主播掏秩,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼荆姆!你這毒婦竟也來(lái)了蒙幻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胞枕,失蹤者是張志新(化名)和其女友劉穎杆煞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腐泻,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡决乎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了派桩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片构诚。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铆惑,靈堂內(nèi)的尸體忽然破棺而出范嘱,到底是詐尸還是另有隱情,我是刑警寧澤员魏,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布丑蛤,位于F島的核電站,受9級(jí)特大地震影響撕阎,放射性物質(zhì)發(fā)生泄漏受裹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一虏束、第九天 我趴在偏房一處隱蔽的房頂上張望棉饶。 院中可真熱鬧,春花似錦镇匀、人聲如沸照藻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幸缕。三九已至群发,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間发乔,已是汗流浹背也物。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留列疗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓浪蹂,卻偏偏與公主長(zhǎng)得像抵栈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坤次,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • 一古劲、volatile的作用詳解 1、防重排序 在并發(fā)環(huán)境下的單例實(shí)現(xiàn)方式缰猴,我們通巢可以采用雙重檢查加鎖(D...
    淡若飄絮閱讀 157評(píng)論 0 1
  • 參考:Java并發(fā)編程:volatile關(guān)鍵字解析 一.內(nèi)存模型的相關(guān)概念 二.并發(fā)編程中的三個(gè)概念 三.Java...
    誰(shuí)在烽煙彼岸閱讀 729評(píng)論 1 1
  • synchronized 是一個(gè)重量級(jí)的鎖,雖然 JVM 對(duì)它做了很多優(yōu)化滑绒。而下面介紹的 volatile 闷堡,則是...
    陳橙ing閱讀 210評(píng)論 0 0
  • 前言 對(duì)Android開發(fā)者來(lái)說(shuō),相信對(duì)并發(fā)編程知識(shí)的掌握是非常薄弱的疑故,一直是個(gè)人進(jìn)階的軟肋之一杠览。對(duì)于并發(fā)實(shí)踐經(jīng)驗(yàn)...
    Android開發(fā)指南閱讀 345評(píng)論 0 4
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭纵势,有人歡樂(lè)有人憂愁踱阿,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評(píng)論 28 53