深入解析volatile關鍵字

文章已同步發(fā)表于微信公眾號JasonGaoH拖云,深入解析volatile關鍵字

volatile關鍵字synchronized關鍵字一樣,在Java多線程開發(fā)中踏施,是一道必須要跨越的檻石蔗。之前有篇文章已經(jīng)分析過synchronized關鍵字的原理,synchronized關鍵字的原理畅形,這一次养距,我們來一步一步分析下volatile關鍵字的工作原理。

本文篇幅稍微有點長日熬,希望您能耐心看下去棍厌,并有所收獲。

volatile關鍵字的使用

首先竖席,我們從一個簡單的程序來入手耘纱。

public class VolatileFoo {
    //init_value的最大值
    final static int MAX = 5;
    //init_value的初始值
    static int init_value = 0;

    public static void main(String[] args) {
        //啟動一個Reader線程,當發(fā)現(xiàn)local_value和init_value不同時毕荐,
        //則輸出init_value被修改的信息
        new Thread(() -> {
            int localValue = init_value;
            while(localValue < MAX) {
                if(init_value != localValue) {
                    System.out.println("this init_value is updated to " + init_value);
                    //對local_value重新賦值
                    localValue = init_value;
                }
            }
        },"Readder").start();
    
        new Thread(() -> {
            int localValue = init_value;
            while(localValue < MAX) {
                System.out.println("this init_value will be changed to " + ++localValue);
                //對local_value重新賦值
                init_value = localValue;
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Updater").start();
    }
}

上面的程序分別啟動了兩個線程束析,一個線程負責對變量進行修改,一個線程負責對變量進行輸出憎亚。

運行程序员寇,輸出結果如下:

this init_value will be changed to 1
this init_value is updated to 1
this init_value will be changed to 2
this init_value will be changed to 3
this init_value will be changed to 4
this init_value will be changed to 5

從輸出信息我們發(fā)現(xiàn),Reader線程沒有感知到init_value的變化第美,我們期望的是在Updater進程更新init_value的值之后蝶锋,Reader進程能夠打印出變化的init_value的值,但結果并不是我們期望的那樣斋日。

我們嘗試在init_value前面加上volatile牲览。

static volatile int init_value = 0;

接著我們再運行下這個程序,輸出結果如下:

this init_value will be changed to 1
this init_value is updated to 1
this init_value will be changed to 2
this init_value is updated to 2
this init_value will be changed to 3
this init_value is updated to 3
this init_value will be changed to 4
this init_value is updated to 4
this init_value will be changed to 5
this init_value is updated to 5

這個時候Reader線程就能夠感受到init_value的值的變化了恶守,并且在條件不滿足時程序就退出了運行第献。

那么為什么加了個volatile就正常了呢, volatile關鍵字的作用到底是什么呢兔港?

想要徹底搞清楚volatile關鍵字庸毫,還需要具備Java內(nèi)存模型、CPU緩存模型衫樊、匯編指令等相關知識的飒赃,接下來,我們接下來一步一步來拆解問題科侈。

CPU緩存模型

要想對volatile有比較深刻的理解载佳,首先我們需要對CPU的緩存模型有一定的認識。

在計算機中臀栈,所有的運算操作都是由CPU的寄存器來完成的蔫慧,CPU指令的執(zhí)行過程需要涉及數(shù)據(jù)的讀取和寫入操作,CPU所能訪問的所有數(shù)據(jù)只能是計算機的主存(通常是指RAM)权薯,雖然CPU的發(fā)展頻率不斷得到提升姑躲,但受制于制造工藝以及成本的限制,計算機的內(nèi)存反倒在訪問速度上沒有多大的突破盟蚣,因此CPU的處理速度和內(nèi)存的訪問速度之間的差距越拉越大黍析,通常這種差距可以達到上千倍,極端情況下甚至會在上萬倍以上屎开。

由于兩邊速度嚴重的不對等阐枣,通過傳統(tǒng)FSB直連內(nèi)存的訪問方式會導致CPU資源受到極大的限制,降低CPU整體的吞吐量奄抽,于是就有了CPU和主內(nèi)存直接增加緩存的設計蔼两,現(xiàn)在緩存數(shù)量都可以增加到3級了,最靠近CPU的緩存為L1,然后依次是L2,L3和主內(nèi)存如孝,CPU緩存模型圖如下所示:


CPU緩存模型圖

Cache的出現(xiàn)是為了解決CPU直接訪問內(nèi)存效率低下的問題宪哩,程序在運行的過程中,會將運算所需要的數(shù)據(jù)從主內(nèi)存復制一份到CPU Cache中第晰,這樣CPU計算時就可以直接對CPU Cache中的數(shù)據(jù)進行讀取和寫入锁孟,當運算結束之后,再將CPU Cache中最新的數(shù)據(jù)刷新到主內(nèi)存當中茁瘦,CPU通過直接訪問Cache的方式提到直接訪問主內(nèi)存的方式極大地提高了CPU的吞吐能力品抽,有個CPU Cache之后,整體的CPU和主內(nèi)存之間的交互的架構大致如下圖所示:


CPU與主內(nèi)存交互圖

Java內(nèi)存模型

由于緩存的出現(xiàn)甜熔,極大地提高了CPU的吞吐能力圆恤,但是同時也引入了緩存不一致的問題。在多處理器系統(tǒng)中腔稀,每個處理器都有自己的的高速緩存盆昙,而它們又共享同一主內(nèi)存羽历,當多個處理器的運算任務都設計到同一塊內(nèi)存區(qū)域時,將可能導致各自的緩存數(shù)據(jù)不一致淡喜,這個時候就需要通過緩存一致性協(xié)議來保證數(shù)據(jù)的正確性秕磷,不同的操作系統(tǒng)使用緩存一致性協(xié)議都各不相同。

因為各種硬件和操作系統(tǒng)的內(nèi)存訪問是有差異的炼团,Java為了程序能在各種平臺下運行達到一致的內(nèi)存訪問效果澎嚣,于是定義了Java內(nèi)存模型(Java Memory Mode,JMM)來對特定內(nèi)存或高速緩存的讀寫訪問過程進行抽象瘟芝。

Java內(nèi)存模型定義了線程和主內(nèi)存之間的抽象關系易桃,具體如下。

  • 共享變量存儲于主內(nèi)存之中锌俱,每個線程都可以訪問晤郑。
  • 每個線程都有私有的工作內(nèi)存和本地內(nèi)存。
  • 工作內(nèi)存值存儲該線程對共享變量的副本嚼鹉。
  • 線程不能直接操作主內(nèi)存贩汉,只有先操作了工作內(nèi)存之后才能寫入主內(nèi)存。
  • 工作內(nèi)存和Java內(nèi)存模型一樣也是一個抽象的概念锚赤,它其實并不存在匹舞,它涵蓋了緩存、寄存器线脚、編譯優(yōu)化以及硬件等赐稽。
    image

    Java內(nèi)存模型定義了一套主內(nèi)存和工作內(nèi)存的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存浑侥、如何從工作內(nèi)存同步到主內(nèi)存之類的實現(xiàn)細節(jié)姊舵。具體有8種操作來完成,分別為lock寓落、unlock括丁、read、load伶选、use史飞、assign、store和write仰税。除此之外构资,Java內(nèi)存模型還規(guī)定在執(zhí)行這8種操作的時候必須滿足8種規(guī)則,由于篇幅問題陨簇,這里就不一一列舉了吐绵,具體可參看深入理解Java虛擬機第12章的Java內(nèi)存模型與線程。

Java內(nèi)存模型是一個抽象的概念,其與計算機硬件的結構并不完全一樣己单,比如計算機物理內(nèi)存不會存在棧內(nèi)存和堆內(nèi)存的劃分唉窃,無論是堆內(nèi)存還是虛擬機棧內(nèi)存都會對應到物理的主內(nèi)存,當然也有一部分堆棧內(nèi)存數(shù)據(jù)可能會存入CPU Cache寄存器中荷鼠。具體可參考下圖:

image

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

介紹了CPU緩存模型以及Java內(nèi)存模型之后句携,我們再來說volatile關鍵字榔幸,這樣更能加深我們對于volatile關鍵字的理解允乐。
volatile關鍵字是Java虛擬機提供的最輕量級的同步機制,很多人由于對它理解不夠削咆,往往更愿意使用synchronized來做同步牍疏。

Java內(nèi)存模型volatile關鍵字定義了一些特殊的訪問規(guī)則,當一個變量被volatile修飾后拨齐,它將具備兩種特性鳞陨,或者說volatile具有下列兩層語義:

  • 第一、保證了不同線程對這個變量進行讀取時的可見性瞻惋, 即一個線程修改了某個變量的值厦滤, 這新值對其他線程來說是立即可見的。 (volatile 解決了線程間共享變量的可見性問題)歼狼。
  • 第二掏导、禁止進行指令重排序, 阻止編譯器對代碼的優(yōu)化羽峰。

針對第一點趟咆,volatile保證了不同線程對這個變量進行讀取時的可見性,具體表現(xiàn)為:

  • 第一: 使用 volatile 關鍵字會強制將在某個線程中修改的共享變量的值立即寫入主內(nèi)存梅屉。
  • 第二: 使用 volatile 關鍵字的話值纱, 當線程 2 進行修改時, 會導致線程 1 的工作內(nèi)存中變量的緩存行無效(反映到硬件層的話坯汤, 就是 CPU 的 L1或者 L2 緩存中對應的緩存行無效);
  • 第三: 由于線程 1 的工作內(nèi)存中變量的緩存行無效虐唠, 所以線程 1再次讀取變量的值時會去主存讀取。

基于這一點惰聂,所以我們經(jīng)常會看到文章中或者書本中會說volatile 能夠保證可見性疆偿。

volatile 能夠保證可見性,但是volatile不能保證程序的原子性庶近。

public class VolatileTest {
    public static volatile int race = 0;
    
    public static void increase() {
        race ++;
    }
    private static final int THREAD_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i =0 ;i<THREAD_COUNT;i++) {
            threads[i] = new Thread(() ->{
                for(int j =0;j< 10000;j++) {
                    increase();
                }
            });
            threads[i].start();
        }
        
        while(Thread.activeCount() > 1)
            Thread.yield();
        
        System.out.println(race);
    }
}

這段代碼發(fā)起了20個線程翁脆,每個線程對race變量進行10000次自增操作,如果這段代碼能夠正確并發(fā)的話鼻种,最后輸出的結果應該是200000反番。我們運行完這段代碼之后,并沒有獲得期望的結果,而且發(fā)現(xiàn)每次運行程序罢缸。輸出的結果都不一樣篙贸,都是一個小于200000的數(shù)字。

問題就出在自增運算”race++“之中枫疆,我們用javap反編譯這段代碼后發(fā)現(xiàn)只有一行代碼的increase()方法在Class文件中是由4條字節(jié)碼指令構成的爵川。

  public static void increase();
    Code:
       0: getstatic     #13                 // Field race:I
       3: iconst_1
       4: iadd
       5: putstatic     #13                 // Field race:I
       8: return

從字節(jié)碼層面上很容易分析出原因了:當getstatic指令把race的值取到操作棧時,volatile關鍵字保證了race的值此時是正確的息楔,但是在執(zhí)行iconst_1寝贡、iAdd這些指令的時候,其他線程可能已經(jīng)把race的值加大了值依,而在操作棧訂的值就變成了過期的數(shù)據(jù)圃泡,所以putstati指令執(zhí)行后就可能把較小的值同步回主內(nèi)存中去了。

其實這里我們通過字節(jié)碼來分析這個問題是不嚴謹?shù)脑赶眨驗榧词咕幾g出來的只有一條字節(jié)指令颇蜡,也并不意味執(zhí)行這條指令就是一個原子操作。一條字節(jié)碼指令在解釋執(zhí)行時辆亏,解釋器將要運行許多行代碼才能實現(xiàn)它的語義风秤,如果是編譯執(zhí)行,一條字節(jié)碼指令也可能轉化成若干條本地機器碼指令扮叨。關于解釋執(zhí)行和編譯執(zhí)行缤弦,我們還會再講到。

由于volatile變量只能保證可見性甫匹,在不符合以下兩條規(guī)則的運算場景中甸鸟,我們?nèi)匀灰ㄟ^加鎖(synchronized或java.util.concurrent中的原子類)來保證原子性。

  • 運輸結果并不依賴變量的當前值兵迅,或者能夠確保只有單一的線程修改變量的值抢韭。
  • 變量不需要與其他狀態(tài)變量共同參與不變約束。

類似下面的場景就時候采用volatile來控制并發(fā)恍箭。

volatile boolean shutdownRequested;
public void shutdown() {
    shutdownRequested = true;
}
public void doWork() {
    while(!shutdownRequested) {
        //do stuff
    }
}

如果我們想讓上面的那個自增操作保持原子性刻恭,我們可以使用AtomicInteger,具體程序如下扯夭,這里就不多做介紹了鳍贾。

import java.util.concurrent.atomic.AtomicInteger;

public class VolatileTest {
//  public static volatile int race = 0;
    public static AtomicInteger race =new  AtomicInteger(0);
    
    public static void increase() {
//      race ++;
        race.incrementAndGet();
    }
    private static final int THREAD_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i =0 ;i<THREAD_COUNT;i++) {
            threads[i] = new Thread(() ->{
                for(int j =0;j< 10000;j++) {
                    increase();
                }
            });
            threads[i].start();
        }
        while(Thread.activeCount() > 1)
            Thread.yield();
        
        System.out.println(race.get());
    }
}

回到volatile關鍵字的第二層語義:禁止指令重排。
普通的變量僅僅會保證在該方法的執(zhí)行過程中所有依賴賦值結果的地方都能獲取到正確的結果交洗,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致骑科。

我們用一段偽代碼來幫助下理解:

Map configOptions;
char[] configText;
//此變量必須定義為volatile
volatile boolean initialized = false;

//假設一下代碼在線程A中執(zhí)行
//模擬讀取配置信息,當讀取完成后將initialized設置為true以通知其他線程配置可用
configOptions = new HashMap();
configText = readConfigFile(fileName);
processCongigOptions(configText,configOptions);
initialized = true

//假設一下代碼在線程B中執(zhí)行
//等待initialized為true构拳,代表線程A已經(jīng)吧配置信息初始化完成
while(!initialized) {
    sleep();
}
//使用線程A中初始化好的配置信息
doSomethingWithConfig();

上面這段代碼如果定義的initialized沒有使用volatile來修飾咆爽,就可能會由于指令重排序的優(yōu)化梁棠,導致位于線程A中最后一句代碼initialized = true被提前執(zhí)行(這里雖然使用Java作為偽代碼,但所指的重排序優(yōu)化是機器級的優(yōu)化操作斗埂,提前執(zhí)行時值這句話對于的匯編代碼被提前執(zhí)行)符糊,這樣在線程B中使用配置信息的代碼就可能出現(xiàn)錯誤,而volatile能避免此類情況的發(fā)生呛凶。

volatile關鍵字深入解析

上面講到volatile關鍵字的兩層語義男娄,那么volatile保證可見性以及有序性到底是如何做到的呢?它的底層邏輯是什么呢漾稀?

這里我們嘗試獲得Java程序的匯編代碼模闲,通過比較變量加入volatile修飾和未加入volatile修飾的區(qū)別。

這里主要使用的是HSDIS插件县好,HSDIS是一個Sun官方推薦的HotSpot虛擬機JIT編譯代碼的反匯編插件围橡,網(wǎng)上有關于這個插件的下載,不過有的鏈接已經(jīng)失效缕贡,我這里是從這里獲取的,hsdis拣播,再把這個clone下來之后晾咪,編譯成功之后,使用下面這個命令拷貝到jre的server目錄,具體可以查看這個repo中README文件贮配,里面寫的很詳細谍倦。

sudo cp build/macosx-amd64/hsdis-amd64.dylib /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/server/

接下來就可以嘗試反匯編了。

public class Singleton {
    private static Singleton instance;
    
    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();
    }
}

上面這個是我們嘗試反匯編的程序代碼泪勒,如果是命令行昼蛀,我們可以使用下面這個指令。

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Singleton

如果是eclipse圆存,在下圖的VM arguments中添加XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly,然后運行程序叼旋,這樣在控制臺就會輸出匯編代碼。

eclipse_jit.png

程序運行后沦辙,在控制臺會輸出很多內(nèi)容,由于輸出太大夫植,所以截取了前面一段輸出。

Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
Loaded disassembler from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/server/hsdis-amd64.dylib
Decoding compiled method 0x0000000112e9ad50:
Code:
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Constants]
  # {method} {0x000000010ce1f000} 'hashCode' '()I' in 'java/lang/String'
  #           [sp+0x40]  (sp of caller)
  0x0000000112e9aec0: mov    0x8(%rsi),%r10d
  0x0000000112e9aec4: shl    $0x3,%r10
  0x0000000112e9aec8: cmp    %rax,%r10
  0x0000000112e9aecb: jne    0x0000000112de0e60  ;   {runtime_call}
  0x0000000112e9aed1: data16 data16 nopw 0x0(%rax,%rax,1)
  0x0000000112e9aedc: data16 data16 xchg %ax,%ax
[Verified Entry Point]
  0x0000000112e9aee0: mov    %eax,-0x14000(%rsp)
  .....

得到這個輸出之后油讯,我使用Singleton全局搜索了下详民,發(fā)現(xiàn)還無結果。

XmXvXX.png

反編譯的卻沒有得到相應的內(nèi)容陌兑,這是什么問題呢沈跨?
帶著這個問題Google了好久,終于搞明白原因了兔综。

于是我們又要來補充些虛擬機編譯的知識了饿凛。

image

我們在使用java -version查看JDK版本的時候隅俘,可以看到最后有個mixed mode,這里其實表明的是Java 虛擬機的編譯方式笤喳,在HotSpot虛擬機中为居,提供了兩種編譯模式:解釋執(zhí)行 和 即時編譯(JIT,Just-In-Time)杀狡,即時編譯也可以稱為編譯執(zhí)行蒙畴,解釋執(zhí)行即逐條翻譯字節(jié)碼為可運行的機器碼,而即時編譯則以方法為單位將字節(jié)碼翻譯成機器碼呜象。

我們在反編譯Singleton這個類的時候膳凝,因為虛擬機使用的是解釋執(zhí)行,這樣我們是得不到匯編代碼的恭陡。在深入理解Java虛擬機一書中介紹可以加上-Xcomp來觸發(fā)JIT編譯蹬音,但是我用的是JDK1.8,這個 -Xcomp`已經(jīng)被移除了休玩,具體哪個版本被移除了著淆,目前我也沒仔細研究過了。

那要怎樣才能觸發(fā)JIT編譯呢拴疤?答案是循環(huán)永部。通過足夠多次數(shù)的循環(huán)來觸發(fā)JIT編譯。我們需要確保寫的Java方法被調用的次數(shù)足夠多呐矾,以觸發(fā)C1(客戶端)編譯苔埋,并大約10000次觸發(fā)C2(服務器)編譯器并打開高級優(yōu)化。換句話說蜒犯,要想查看匯編代碼组橄,我們所寫的Java源代碼文件不能太過于簡單,要足夠復雜罚随。

注意:C1玉工,C2都是HotSpot虛擬機內(nèi)置的即時編譯器。C1:即Client編譯器毫炉,面向對啟動性能有要求的客戶端GUI程序瓮栗,采用的優(yōu)化手段比較簡單,因此編譯的時間較短瞄勾。C2:即Server編譯器费奸,面向對性能峰值有要求的服務端程序,采用的優(yōu)化手段復雜进陡,因此編譯時間長愿阐,但是在運行過程中性能更好。

public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if(instance ==null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            print();
        }
    }
    private static void print() {
        for(int i =0;i<=1000;i++) {
            Singleton.getInstance();
        }
    }
}

于是我在代碼里加上了兩層循環(huán)趾疚,然后在嘗試獲取一些匯編代碼缨历。

這次發(fā)現(xiàn)終于能得到Singleton相關的匯編代碼了以蕴。

于是我們分別編譯了兩次,第一個是沒有使用volatile關鍵字修飾instance辛孵,第二個是使用volatile關鍵字丛肮,然后我們分別取出Singleton::getInstance這一段來進行比較。

 // 未使用volatile修飾
  0x000000010d29e931: movabs $0x7955f12a8,%rsi  ;   {oop(a 'java/lang/Class' = 'main/Singleton')}
  0x000000010d29e93b: mov    %rax,%r10
  0x000000010d29e93e: shr    $0x3,%r10
  0x000000010d29e942: mov    %r10d,0x68(%rsi)
  0x000000010d29e946: shr    $0x9,%rsi
  0x000000010d29e94a: movabs $0xfe403000,%rax
  0x000000010d29e954: movb   $0x0,(%rsi,%rax,1)  ;*putstatic instance
                                                ; - main.Singleton::getInstance@24 (line 10)
// 使用volatile修飾
 0x000000011435394f: movabs $0x7955f12a8,%rsi  ;   {oop(a 'java/lang/Class' = 'main/Singleton')}
  0x0000000114353959: mov    %rax,%r10
  0x000000011435395c: shr    $0x3,%r10
  0x0000000114353960: mov    %r10d,0x68(%rsi)
  0x0000000114353964: shr    $0x9,%rsi
  0x0000000114353968: movabs $0x10db6e000,%rax
  0x0000000114353972: movb   $0x0,(%rsi,%rax,1)
  0x0000000114353976: lock addl $0x0,(%rsp)     ;*putstatic instance
                                                ; - main.Singleton::getInstance@24 (line 10)

雖然對于匯編指令了解不多魄缚,但還是能從兩個對比中看出差異所在宝与。
很明顯,在movb $0x0,(%rsi,%rax,1) 之后冶匹,加了volatile修飾的匯編代碼后面多了一條匯編指令lock addl $0x0,(%rsp)习劫,這個操作相當于一個內(nèi)存屏障,指令重排時不能把后面的指令重排序到內(nèi)存屏障之前的位置嚼隘,當只有一個CPU訪問內(nèi)存時诽里,并不需要內(nèi)存屏障,當如果有兩個或多個CPU訪問同一塊內(nèi)存飞蛹,且其中有一個在觀測另一個谤狡,就需要內(nèi)存屏障來保證一致性了。lock addl $0x0,(%rsp) 表示把rsp的寄存器的值加0桩皿,這顯然是一個空操作豌汇,關鍵在于lock前綴。

L6wWZt.jpg

查詢IA32手冊泄隔,lock前綴會強制執(zhí)行原子操作,它的作用是是的本CPU的Cache寫入了內(nèi)存宛徊,該寫入動作會引起別的CPU無效化其Cache佛嬉。所有通過這樣一個空操作,可讓前面volatile變量的便是對其他CPU可見闸天。

那為什么說它能禁止指令重排呢暖呕?從硬件架構上講,指令重排序是指CPU采用了運行將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應的點了單元處理苞氮,但并不是指令任意重排湾揽,CPU需要能正確處理指令依賴情況以保障程序能得出正確的執(zhí)行結果。lock addl $0x0,(%rsp) 指令把修改同步到內(nèi)存時笼吟,意味著所有之前的操作都已經(jīng)執(zhí)行完成库物,這樣便形成了" 指令重排序無法越過內(nèi)存屏障"的效果。

總結來說贷帮,內(nèi)存屏障有兩個作用:
先于這個內(nèi)存屏障的指令必須先執(zhí)行戚揭, 后于這個內(nèi)存屏障的指令必須后執(zhí)行。
如果你的字段是volatile撵枢,在讀指令前插入讀屏障民晒,可以讓高速緩存中的數(shù)據(jù)失效精居,重新從主內(nèi)存加載數(shù)據(jù)。在寫指令之后插入寫屏障潜必,能讓寫入緩存的最新數(shù)據(jù)寫回到主內(nèi)存靴姿。

關于volatile關鍵字的介紹就到這里了,感謝磁滚,如果覺得還可以請幫忙點個贊链方,有問題歡迎留言討論柔吼。

原文鏈接

參考

[深入理解Java虛擬機]
[Java高并發(fā)編程詳解]

最后編輯于
?著作權歸作者所有,轉載或內(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
  • 正文 為了忘掉前任崔慧,我火速辦了婚禮,結果婚禮上穴墅,老公的妹妹穿的比我還像新娘惶室。我一直安慰自己,他們只是感情好玄货,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布皇钞。 她就那樣靜靜地躺著,像睡著了一般誉结。 火紅的嫁衣襯著肌膚如雪鹅士。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天惩坑,我揣著相機與錄音掉盅,去河邊找鬼也拜。 笑死,一個胖子當著我的面吹牛趾痘,可吹牛的內(nèi)容都是我干的慢哈。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼永票,長吁一口氣:“原來是場噩夢啊……” “哼卵贱!你這毒婦竟也來了?” 一聲冷哼從身側響起侣集,我...
    開封第一講書人閱讀 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級特大地震影響旗唁,放射性物質發(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

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