volatile

JMM

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

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

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

特點(diǎn):

  1. 原子性:即一個(gè)操作或多個(gè)操作在執(zhí)行的過程中,要成功都成功,要失敗都失敗。
  2. 可見性:多個(gè)線程訪問同一個(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ī)則(來源 深入理解 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è),我們可以通過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ù)一致性的問題(可見性問題)

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

volatile

volatile 可以理解為輕量級(jí)的synchronized。在多線程的開發(fā)過程中,保證了內(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ā)過程中不要這么寫,可能會(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無法保證復(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
}

通過上面的代碼可以看到,a++的操作在java中看起來是一個(gè)操作,但是在執(zhí)行的時(shí)候,被分為了下面三個(gè)操作;那么問題就出現(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é)果的前提之前,可以重新安排語句的執(zhí)行順序
  • 處理器重排序:不存在數(shù)據(jù)依賴性,處理器可以改變語句對(duì)應(yīng)的機(jī)器碼的執(zhí)行順序。
volatile如何做到的禁止重排序?

內(nèi)存屏障 ( Memory Barrier)
由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化。如果在指令間插入一條Memory Barrier則會(huì)告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說通過插入內(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ì)象的過程:
 * 1、 memory = allocate()  分配對(duì)象內(nèi)存空間
 * 2、ctorInstance()  初始化對(duì)象
 * 3、instance = memory 設(shè)置instance指向剛才的分配的內(nèi)存
 * <p>
 * 不安全的原因:
 * cpu和jvm優(yōu)化,發(fā)生了指令重排序
 * <p>
 * 上面的過程變成了
 * 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、volatile的作用詳解 1、防重排序 在并發(fā)環(huán)境下的單例實(shí)現(xiàn)方式,我們通??梢圆捎秒p重檢查加鎖(D...
    淡若飄絮閱讀 180評(píng)論 0 1
  • synchronized 是一個(gè)重量級(jí)的鎖,雖然 JVM 對(duì)它做了很多優(yōu)化。而下面介紹的 volatile ,則是...
    陳橙ing閱讀 464評(píng)論 0 0
  • 前言 對(duì)Android開發(fā)者來說,相信對(duì)并發(fā)編程知識(shí)的掌握是非常薄弱的,一直是個(gè)人進(jìn)階的軟肋之一。對(duì)于并發(fā)實(shí)踐經(jīng)驗(yàn)...
    Android開發(fā)指南閱讀 430評(píng)論 0 4
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,814評(píng)論 28 54
  • 信任包括信任自己和信任他人 很多時(shí)候,很多事情,失敗、遺憾、錯(cuò)過,源于不自信,不信任他人 覺得自己做不成,別人做不...
    吳氵晃閱讀 6,355評(píng)論 4 8

友情鏈接更多精彩內(nèi)容