亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

什么是volatile機制

發布時間:2021-10-12 10:52:17 來源:億速云 閱讀:164 作者:iii 欄目:編程語言

本篇內容介紹了“什么是volatile機制”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

前提概要

我們都知道synchronized關鍵字的特性:原子性、可見性、有序性、可重入性,雖然,JDK在不斷的嘗試優化這個內置鎖,一文中有提到:無鎖 -> 偏向鎖 -> 輕量鎖 -> 重量鎖 一共四種狀態,但是,在高并發的情況下且大量沖突出現的時候,最終都還是會膨脹到重量鎖

為何這么說?

那是因為,synchronized是同步代碼塊通過monitor監視器,對整個代碼塊(方法是通過判斷 ACC_SYNCHRONZED 標志位對整個方法)進行了整體原子性操作。而 volatile 對單一操作是原子性的,非單一操作則是非原子性的

基本用法

Java語言里的volatile關鍵字是用來修飾變量的,方式如下入所示。表示:該變量需要直接存儲到主內存中

public class SharedClass {
    public volatile int counter = 0;
}

被volatile關鍵字修飾的 int counter 變量會直接存儲到主內存中并且所有關于該變量的讀操作,都會直接從主內存中讀取,而不是直接從CPU緩存。(關于主內存和CPU緩存的區別,如果不理解也不用擔心,下面會詳細介紹

這么做解決什么問題呢?主要是兩個問題:

  • 多線程見可見性的問題

  • CPU指令重排序的問題

注:為了描述方便,我們接下來會把 volatile 修飾的變量簡稱為“volatile 變量”,把沒有用 volatile 修飾的變量建成為“non-volatile”變量。

理解 volatile 關鍵字

變量可見性問題(Variable Visibility Problem) : volatile可以保證變量變化在多線程間的可見性

一個多線程應用中,出于計算性能的考慮,每個線程默認是從主內存將該變量拷貝到線程所在CPU的緩存中,然后進行讀寫操作的。現在電腦基本都是多核CPU,不同的線程可能運行的不同的核上,而每個核都會有自己的緩存空間。如下圖所示(圖中的 CPU 1,CPU 2 大家可以直接理解成兩個核)

什么是volatile機制

這里存在一個問題,JVM既不會保證什么時候把 CPU 緩存里的數據寫到主內存,也不會保證什么時候從主內存讀數據到 CPU 緩存。也就是說,不同 CPU 上的線程,對同一個變量可能讀取到的值是不一致的,這也就是我們通常說的:線程間的不可見問題

比如下圖,Thread 1 修改的 counter = 7 只在 CPU 1 的緩存內可見,Thread 2 在自己所在的 CPU 2 緩存上讀取 counter 變量時,得到的變量 counter 的值依然是 0。

什么是volatile機制

而volatile出現的用意之一,就是要解決線程間不可見性,通過 volatile 修飾的變量,都會變得線程間可見

其解決方式就是文章開頭提到的:

  • 通過 volatile 修飾的變量,所有關于該變量的讀操作,都會直接從主內存中讀取,而不是 CPU 自己的緩存。而所有該變量的寫操都會寫到主內存上。

  • 因為主內存是所有 CPU 共享的,理所當然即使是不同 CPU 上的線程也能看到其他線程對該變量的修改了。volatile不僅僅只保證 volatile變量的可見性,volatile 在可見性上所做的工作,實際上比保證 volatile 變量的可見性更多

當 Thread A 修改了某個被 volatile 變量 V,另一個 Thread B 立馬去讀該變量 V。一旦 Thread B 讀取了變量 V 后,不僅僅是變量 V 對 Thread B 可見, 所有在 Thread A 修改變量 V 之前 Thread A 可見的變量,都將對 Thread B 可見。

當 Thread A 讀取一個 volatile 變量 V 時,所有對于 Thread A 可見的其他變量也都會從主內存中被讀取。

特性及原理

可見性

任意一個線程修改了 volatile 修飾的變量,其他線程可以馬上識別到最新值。實現可見性的原理如下。

  • 步驟 1:修改本地內存,強制刷回主內存。

什么是volatile機制

  • 步驟 2:強制讓其他線程的工作內存失效過期。(此部分更多的屬于MESI協議)

什么是volatile機制

單個讀/寫具有原子性

單個volatile變量的讀/寫(比如 vl=l)具有原子性,復合操作(比如 i++)不具有原子性,Demo 代碼如下:

public class VolatileFeaturesA {
   
   private volatile long vol = 0L;

    /**
     * 單個讀具有原子性
     * @date:2020 年 7 月 14 日 下午 5:02:38
     */
    public long get() {
        return vol;
    }

    /**
     * 單個寫具有原子性
     * @date:2020 年 7 月 14 日 下午 5:01:49
     */
    public void set(long l) {
        vol = l;
    }

    /**
     * 復合(多個)讀和寫不具有原子性
     * @date:2020 年 7 月 14 日 下午 5:02:24
     */
    public void getAndAdd() {
        vol++;
    }

}

互斥性

同一時刻只允許一個線程操作 volatile 變量,volatile 修飾的變量在不加鎖的場景下也能實現有鎖的效果,類似于互斥鎖。上面的 VolatileFeaturesA.java 和下面的 VolatileFeaturesB.java 兩個類實現的功能是一樣的(除了 getAndAdd 方法)。

public class VolatileFeaturesB {
    
	private volatile  long vol = 0L;

    /**
     * 普通寫操作
     * @date:2020 年 7 月 14 日 下午 8:18:34
     * @param l
     */
    public synchronized void set(long l) {  
        vol = l;
    }

    /**
     * 加 1 操作
     * @author songjinzhou
     * @date:2020 年 7 月 14 日 下午 8:28:25
     */
    public void getAndAdd() {
        long temp = get();
        temp += 1L;
        set(temp);
    }

    /**
     * 普通讀操作
     * @date:2020 年 7 月 14 日 下午 8:33:00
     * @return
     */
    public synchronized long get() {
        return vol;
    }
}

部分有序性

JVM 是使用內存屏障來禁止指令重排,從而達到部分有序性效果,看看下面的 Demo 代碼分析自然明白為什么只是部分有序

//a、b 是普通變量,flag 是 volatile 變量
int a = 1;            //代碼 1
int b = 2;            //代碼 2
volatile boolean flag = true;  //代碼 3
int a = 3;            //代碼 4
int b = 4;            //代碼 5

因為 flag 變量是使用 volatile 修飾,則在進行指令重排序時,不會把代碼 3 放到代碼 1 和代碼 2 前面,也不會把代碼 3 放到代碼 4 或者代碼 5 后面。 但是指令重排時代碼 1 和代碼 2 順序、代碼 4 和代碼 5 的順序不在禁止重排范圍內,比如:代碼 2 可能會被移到代碼 1 之前。

內存屏障類型分為四類。

    1. LoadLoadBarriers

指令示例:LoadA —> Loadload —> LoadB

此屏障可以保證 LoadB 和后續讀指令都可以讀到 LoadA 指令加載的數據,即讀操作 LoadA 肯定比 LoadB 先執行


    1. StoreStoreBarriers

指令示例:StoreA —> StoreStore —> StoreB

此屏障可以保證 StoreB 和后續寫指令可以操作 StoreA 指令執行后的數據,即寫操作 StoreA 肯定比 StoreB 先執行


    1. LoadStoreBarriers

指令示例: LoadA —> LoadStore —> StoreB

此屏障可以保證 StoreB 和后續寫指令可以讀到 LoadA 指令加載的數據,即讀操作 LoadA 肯定比寫操作 StoreB 先執行


    1. StoreLoadBarriers

指令示例:StoreA —> StoreLoad —> LoadB

此屏障可以保證 LoadB 和后續讀指令都可以讀到 StoreA 指令執行后的數據,即寫操作 StoreA 肯定比讀操作 LoadB 先執行。

實現有序性的原理:

如果屬性使用了 volatile 修飾,在編譯的時候會在該屬性的前或后插入上面介紹的 4 類內存屏障來禁止指令重排,比如

  • volatile 寫操作的前面插入 StoreStoreBarriers 保證volatile寫操作之前的普通讀寫操作執行完畢后再執行 volatile 寫操作。

  • volatile 寫操作的后面插入 StoreLoadBarriers 保證 volatile 寫操作后的數據刷新到主內存,保證之后的 volatile 讀寫操作能使用最新數據(主內存)。

  • volatile 讀操作的后面插入 LoadLoadBarriersLoadStoreBarriers 保證 volatile 讀寫操作之后的普通讀寫操作先把線程本地的變量置為無效,再把主內存的共享變量更新到本地內存,之后都使用本地內存變量

volatile 讀操作內存屏障:

什么是volatile機制

volatile 寫操作內存屏障:

什么是volatile機制

狀態標志,比如布爾類型狀態標志,作為完成某個重要事件的標識,此標識不能依賴其他任何變量,Demo 代碼如下:

public class Flag {
    //任務是否完成標志,true:已完成,false:未完成
    volatile boolean finishFlag;

    public void finish() {
        finishFlag = true;
    }

    public void doTask() { 
        while (!finishFlag) { 
            //keep do task
        }
    }

一次性安全發布,比如:著名的 double-checked-locking,demo 代碼上面已貼出。 開銷較低的讀,比如:計算器,Demo 代碼如下。

/**
 * 計數器
 */
public class Counter {
    private volatile int value;
    //讀操作無需加鎖,減少同步開銷提交性能,使用 volatile 修飾保證讀操作的可見性,每次都可以讀到最新值 
    public int getValue() {
        return value; 
    }
    //寫操作使用 synchronized 加鎖,保證原子性
    public synchronized int increment() {
        return value++;
    }
}

“什么是volatile機制”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

监利县| 马尔康县| 阳信县| 潞西市| 丽江市| 哈密市| 武隆县| 神农架林区| 牡丹江市| 蓝田县| 同江市| 四川省| 肥乡县| 嘉鱼县| 绥江县| 黑山县| 沛县| 鱼台县| 尚义县| 文成县| 衡东县| 黄梅县| 临湘市| 尉氏县| 武平县| 金门县| 阿巴嘎旗| 平阳县| 辽宁省| 阿坝| 宾川县| 古浪县| 台山市| 临猗县| 拜城县| 阳新县| 磐石市| 潞西市| 海林市| 伽师县| 平定县|