[面試必備]深刻理解Java的volatile關鍵字

img

前言

在Java併發編程中,volatile關鍵字有着相當重要的做用,在面試中也經常會是必備的一個問題。本文將會介紹volatile關鍵字的做用以及其實現原理。java

volatile做用

volatile在併發編程中扮演着重要的角色,volatile是輕量級的synchronized,volatile關鍵字有兩個做用:nginx

1)保證共享變量的可見性

可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。筆者此前一篇文章Java併發編程:Java內存模型JMM中有說到,Java內存模型中有主內存和本地內存之分,本地內存持有共享變量的一份副本,線程對共享變量的修改是先修改本地內存的副本,而後再回寫到主內存中去。面試

可能存在這樣的狀況,線程A和線程B同時去修改一個共享變量C,假設線程A先對共享變量C作了修改,而此時線程B卻沒能及時感知到共享變量C已經發生了改變,緊接着B對本地過時的副本數據進行了修改,這形成了共享變量的不可見問題。編程

而使用了volatile關鍵字修改的共享變量,當線程修改了共享變量以後,會立馬刷新到主內存中,而且會使其餘線程緩存了該地址的數據失效,這就保證了線程之間共享變量的可見性。緩存

2)防止指令重排序

volatile關鍵字的另一個做用就是防止指令重排序。代碼在實際執行過程當中,並不全是按照編寫的順序進行執行的,在保證單線程執行結果不變的狀況下,編譯器或者CPU可能會對指令進行重排序,以提升程序的執行效率。可是在多線程的狀況下,指令重排序可能會形成一些問題,最多見的就是雙重校驗鎖單例模式:多線程

public class SingletonSafe {

    private static volatile SingletonSafe singleton;

    private SingletonSafe() {
    }

    public static SingletonSafe getSingleton() {
        if (singleton == null) {
            synchronized (SingletonSafe.class) {
                if (singleton == null) {
                    singleton = new SingletonSafe();
                }
            }
        }
        return singleton;
    }
}
複製代碼

若是沒有使用volatile關鍵字,則可能會出現其餘線程獲取了一個未初始化完成的singleton對象,具體緣由筆者不在這裏贅述了,有興趣地同窗能夠搜索一下「double checked locking with delay initialization」學習下,筆者後續有時間再寫篇文章分析下。併發

volatile實現原理

1)可見性實現原理

對於volatile關鍵字修飾的變量,當對volatile變量進行寫操做的時候,JVM會向處理器發送一條lock前綴的指令,將這個緩存中的變量回寫到系統主存中。可是就算寫回到內存,若是其餘處理器緩存的值仍是舊的,再執行計算操做就會有問題,因此在多處理器下,爲了保證各個處理器的緩存是一致的,就會實現緩存一致性協議。app

緩存一致性協議:每一個處理器經過嗅探在總線上傳播的數據來檢查本身緩存的值是否是過時了,當處理器發現本身緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操做的時候,會強制從新從系統內存裏把數據讀處處理器緩存裏。函數

因此,若是一個變量被volatile所修飾的話,在每次數據變化以後,其值都會被強制刷入主存。而其餘處理器的緩存因爲遵照了緩存一致性協議,也會把這個變量的值從主存加載到本身的緩存中。這就保證了一個volatile在併發編程中,其值在多個緩存中是可見的。post

2)防止指令重排序實現原理

volatile防止指令重排序是經過內存屏障來實現的。內存屏障分爲以下三種:

Store Barrier

Store屏障,是x86的」sfence「指令,強制全部在store屏障指令以前的store指令,都在該store屏障指令執行以前被執行。

Load Barrier

Load屏障,是x86上的」ifence「指令,強制全部在load屏障指令以後的load指令,都在該load屏障指令執行以後被執行

Full Barrier

Full屏障,是x86上的」mfence「指令,複合了load和save屏障的功能。

Java內存模型中volatile變量在寫操做以後會插入一個store屏障,在讀操做以前會插入一個load屏障。一個類的final字段會在初始化後插入一個store屏障,來確保final字段在構造函數初始化完成並可被使用時可見。也正是JMM在volatile變量讀寫先後都插入了內存屏障指令,進而保證了指令的順序執行。

原創聲明

本文發佈於掘金號【Happyjava】。Happy的掘金地址:juejin.im/user/5cc289…,Happy的我的博客:blog.happyjava.cn。歡迎轉載,但須保留此段聲明。

關注公衆號領資料

搜索公衆號【Happyjava】,回覆【電子書】和【視頻】,便可獲取大量優質電子書和大數據、kafka、nginx、MySQL等視頻資料

關注Happyjava公衆號
相關文章
相關標籤/搜索