java 中的volatile關鍵字

1.volatile實現可見性的原理是什麼?

volatile變量修飾的共享變量進行寫操作的時候匯編代碼會多出一個Lock前綴指令。

在該指令下,多核處理器會引發兩件事:

  • 將當前處理器緩存行的數據寫回系統內存
  • 這個寫回內存的操作會使在其他CPU裡緩存瞭該內存地址的數據無效

這裡需要簡單瞭解CPU緩存一致性問題:多核處理器環境下,每個CPU都有自己的緩存行,緩存瞭內存中的數據,要維護多個CPU中緩存的數據一致性,就需要解決兩個問題:

  • 一是寫傳播(某個CPU裡的cache數據更新時,需要傳播到其他CPU的cache中);
  • 二是事務的串行化執行(在某個CPU裡對數據的修改,在其他CPU中看起來順序是一樣的,也就是要引入近似[鎖]的概念,保證同一時刻隻有一個CPU可以對數據做修改);

寫傳播是通過[總線嗅探]完成的:通過總線把修改數據的事件廣播通知給其他所有的核心,每個CPU核心都會監聽總線上的廣播事件,並檢查是否有相同的數據在自己的Cache裡面;而事務的串行化則通過[MESI協議]來完成。

MESI(Modified(已修改)、Exclusive(獨占)、Shared(共享)、Ivalidated(已失效))協議中,如果要修改一個共享數據,不能直接修改,要先向其他CPU廣播一個請求,把其他CPU cache中對應的數據狀態改為Invalidated;以後其他CPU在讀取標記為Invalidated的數據時,需要強制從內存中讀取數據。

2.演示volatile的可見性

public class VolatileDemo {
    static  int flag = 1;  // 定義一個共享變量
    public static void main(String[] args) {
        // 兩個線程,一個線程負責讀取flag的值,另一個線程負責修改flag的值
        new Thread(){
            int localflag = flag;
            @Override
            public void run() {
                while(true){
                    //flag被修改後就跟localflag不一樣瞭
                    if(localflag!=flag){
                        System.out.println("讀到瞭flag修改後的值:"+ flag);
                        //把讀到的值賦值給本地變量
                        localflag = flag;
                    }
                }
            }
        }.start();

        new Thread(){
            int localflag = flag;
            @Override
            public void run() {
                while (true){
                    //一直對flag的值進行修改
                    System.out.println("對flag的值進行修改:"+ ++localflag);
                    flag = localflag;
                    //休眠一秒更好地觀察結果
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();


    }
}

可以看到另一個線程並不能及時讀取到被修改的值。

共享變量用volatile修飾後:

public class VolatileDemo {
    static  volatile int flag = 1;
    public static void main(String[] args) {
        // 兩個線程,一個線程負責讀取flag的值,另一個線程負責修改flag的值
        new Thread(){
            int localflag = flag;
            @Override
            public void run() {
                while(true){
                    //flag被修改後就跟localflag不一樣瞭
                    if(localflag!=flag){
                        System.out.println("讀到瞭flag修改後的值:"+ flag);
                        //把讀到的值賦值給本地變量
                        localflag = flag;
                    }
                }
            }
        }.start();

        new Thread(){
            int localflag = flag;
            @Override
            public void run() {
                while (true){
                    //一直對flag的值進行修改
                    System.out.println("對flag的值進行修改:"+ ++localflag);
                    flag = localflag;
                    //休眠一秒更好地觀察結果
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();


    }
}

可以看到用volatile修飾後,每次另一個線程總能讀取到修改後的值。

到此這篇關於java 中的volatile關鍵字的文章就介紹到這瞭,更多相關volatile關鍵字內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: