JAVA是如何運行的四-voliate

綜合上篇文章的代碼一的說明

如果我們使用voliate關鍵字修飾flag會出現什麼效果

public static volatile boolean flag = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!flag){
                }
                System.out.println("線程1結束");
            }
        }).start();
        Thread.sleep(5000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                flag = true;
                System.out.println("線程2結束");
            }
        }).start();
    }

看下執行結果:


線程2結束
線程1結束

Process finished with exit code 0

明顯可以看到當添加關鍵字修飾時,當線程二改變flag的值以後,線程一明顯是感受到了主內存的變化並且從主內存重新獲取flag的最新值,那voliate我們僅僅加個關鍵字,jvm到底做了哪些東西?爲明確這點,先來看一個概念

在併發編程中的三個問題:原子性,可見性,有序性

1.原子性

  原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

(1)原子的意思代表着——“不可分”;
        (2)在整個操作過程中不會被線程調度器中斷的操作,都可認爲是原子性。原子性是拒絕多線程交叉操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作

2.可見性

  可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

    變量經過volatile修飾後,對此變量進行寫操作時,彙編指令中會有一個LOCK前綴指令,加了這個指令後,會引發兩件事情:

  • 發生修改後強制將當前處理器緩存行的數據寫回到系統內存
  • 這個寫回內存的操作會使得在其他處理器緩存了該內存地址無效,重新從內存中讀取。

3.有序性

  有序性:即程序執行的順序按照代碼的先後順序執行。

在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。

 

通過上述的例子可以很明確的一點就是:voliate保證了可見性

那voliate是不是保證了原子性呢?

結合第二個代碼來看下

    public static volatile int flag = 0;
    public static  void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public  void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    flag++;
                    System.out.println("線程一"+flag);
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public  void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    flag++;
                    System.out.println("線程二"+flag);
                }
            }
        }).start();
    }

我們給flag加上voliate關鍵字,如果voliate保證一致性,那程序的執行結果肯定是100。結果顯示flag最後的累加值並不等於100,所以

voliate關鍵字是無法保證一致性的

那voliate是不是保證了有序性呢?

    static int a, b;

    static int x, y;

    public static void main(String[] args) {
        int i = 0;
        for (; ; ) {//死循環
            a = 0;
            b = 0;
            x = 0;
            y = 0;
            i++;
            Thread aThread1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            Thread bThread1 = new Thread(() -> {
                b = 1;
                y = a;
            });
            aThread1.start();
            bThread1.start();
            try {
                aThread1.join();
                bThread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("第" + i + "次循環輸出:" + "x=" + x + ";y=" + y);

            if (x == 0 && y == 0) {
                break;
            }

        }

    }

運行結果後發現有可能會出現x = 0 y = 0的情況,

x = 0 y = 0  執行順序: x = b; a = 1; y = a; b = 1;

x = 0 y = 1  執行順序: x = b; a = 1; b = 1; y = a;

x = 1 y = 0  執行順序: a = 1; x = b; y = a; b = 1;

x = 1 y = 1  執行順序: a = 1; b = 1; x = b; y = a;

那就說明java代碼發生了重排序,先執行x = b = 0,y = a = 0 後執行a = 1,b = 1

當給變量加上voliate關鍵字

    static volatile int a, b;

    static int x, y;

執行結果只有兩種.兩種結果的差異完全是在兩個線程誰先執行的問題上,而不是重排序的問題上。

x = 1 y = 0  執行順序: b = 1; y = a;
                                   a = 1; x = b;

x = 0 y = 1  執行順序: a = 1;x = b;
                                    b = 1; y = a;

總結下就是:

x = 1;        //語句1
y = 2;        //語句2
flag = true;  //語句3
x = 3;         //語句4
y = 4;       //語句5

當flag被voliate修飾後,無論如何進行重排序,語句3永遠在第三次執行,1,2永遠在3前面執行。4,5永遠在3後面執行

在前面提到volatile關鍵字能禁止指令重排序,所以volatile能在一定程度上保證有序性。

  volatile關鍵字禁止指令重排序有兩層意思:

  1)當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;

  2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。

所以voliate關鍵字保證了變量的可見性,以及一定程度上的有序性,但無法保證原子性

 

接下看看voliate關鍵字到底做了什麼?

 

 

 

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