【多線程】Java中的volatile到底起着什麼樣的作用呢

當聲明共享變量爲volatile後,對這個變量的讀/寫將會很特別。那麼它到底起着怎樣的作用呢?

一、可見性

可見性指的是線程之間的可見性,一個線程修改的狀態對另一個線程是可見的。也就是一個線程對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最後的寫入。
其實之所以要保證可見性,主要與Java的內存模型有關。

內存模型

每個線程在執行的時候,會從主內存中拷貝一份到自己的本地內存,線程操作的時候是操作自己本地內存的變量,這樣每個線程都操作自己本地內存的變量,就可能導致這個共享變量的數據不一致,這就體現了volatile的作用了。volatile可以使得一個線程操作共享變量的時候,能讀到這個共享變量最新的變化,在這個變化上操作。

示意圖

在這裏插入圖片描述

線程對共享變量的所有操作都必須在⾃⼰的本地內存中進⾏,不能直接從主內存中讀取。JMM通過控制主內存與每個線程的本地內存之間的交互,來提供內存可⻅性保證。

舉個栗子

下邊這個例子只是爲了理解volatile的作用
對任意單個volatile變量的讀/寫具有原子性,但類似於volatile++這種複合操作不具有原子性

線程A和線程B操作同一個變量a,假設線程A先執行,線程B後執行
在這裏,因爲有了volatile,所以線程A執行後的結果a=1對B是可見的,B執行的時候是在a=1的基礎上進行的,然後B執行完是2;
如果沒有volatile,線程A執行完a=1,但線程B可能不知道,B執行的時候以爲a=0,B執行完就是1了

public class VolatileDemo {
    private static volatile int a = 0;
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            a++;
            System.out.println(a);
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
           a++;
            System.out.println(a);
        }
    }
}

執行過程

最開始共享變量flag=0。也就是主內存中flag=0,線程A執行的時候,是先從主內存中拷貝一份到本地內存A,flag=0,然後線程A操作flag=1;A操作的flag=1更新到主內存中,接着B開始執行,主內存中flag=1,更新到本地內存B中,flag=1,接着B操作flag=2。

在這裏插入圖片描述

二、禁止重排序

在Java內存模型中,對一些語句進行重排序,可提升性能,但是有的地方一旦重排序了,得到的結果就不是我們想要的結果了。也就是我們寫代碼的時候看到的是語句1在語句2前,但在JMM中可能重排序後就是語句2在語句1前邊了,但我們可能就想讓語句1在語句2前執行,這就用到了volatile了,它是怎麼實現不重排序呢,JVM通過內存屏障來實現限制處理器的重排序

重排序

計算機在執⾏程序時,爲了提⾼性能,編譯器和處理器常常會對指令做重排。
原理是指令1還沒有執⾏完,就可以開始執⾏指令2,⽽不⽤等到指令1執⾏結束之後再執⾏指令2,這樣就⼤⼤提⾼了效率。
指令重排可以保證串⾏語義⼀致,但是沒有義務保證多線程間的語義也⼀致。所以在多線程下,指令重排序可能會導致⼀些問題。

內存屏障

分類

讀屏障(Load Barrier)和寫屏障(Store Barrier)

作⽤

(1) 阻⽌屏障兩側的指令重排序;
(2)強制把寫緩衝區/⾼速緩存中的髒數據等寫回主內存,或者讓緩存中相應的數據失效

基於保守策略的JMM內存屏障插⼊策略

在每個volatile寫操作前插⼊⼀個StoreStore屏障;
在每個volatile寫操作後插⼊⼀個StoreLoad屏障;
在每個volatile讀操作後插⼊⼀個LoadLoad屏障;
在每個volatile讀操作後再插⼊⼀個LoadStore屏障。
在這裏插入圖片描述

實際過程中的JMM內存屏障插⼊

class VolatileBarrier{
    int a;
    volatile int b=1;
    volatile int c=2;
    void readWrite(){
        // 第一個volatile讀
        int i=b;
        // 第二個volatile讀
        int j=c;
        // 普通寫
        a=i+j;
        // 第一個volatile寫
        b=i+1;
        // 第二個volatile寫
        c=j+1;
    }
}

在這裏插入圖片描述

在保證內存可⻅性這⼀點上,volatile有着與鎖相同的內存語義,所以可以作爲⼀個“輕量級”的鎖來使⽤。但由於volatile僅僅保證對單個volatile變量的讀/寫具有原⼦性,⽽鎖可以保證整個臨界區代碼的執⾏具有原⼦性。所以在功能上,鎖⽐volatile更強⼤;在性能上,volatile更有優勢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章