精通Java中的volatile關鍵字

在一些開源的框架的源碼當中時不時均可以看到volatile這個關鍵字,最近特地學習一下volatile關鍵字的使用方法。java

不少資料中是這樣介紹volatile關鍵字的:安全

volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的「可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。

文字不太好理解,經過例子來理解。微信

一、例子

首先看一個沒有使用volatile關鍵字例子:框架

package com.swnote.java;

/**
 * volatile測試例子
 *
 * @author lzj
 * @date [2019-04-47]
 */
public class VolatileTest {

    private boolean flag;

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        test.test();
    }

    public void test() {
        new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
        }).start();

        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println("thread flag = " + flag);
                }
            }
        }).start();
    }
}

該例子中定義了一個flag共享變量,test方法裏面開啓了兩個線程,第一個線程在等待1秒中後修改共享變量flag的值爲true,第二個線程經過循環判斷flag的值,當flag的值爲true時,輸出內容。學習

此時有兩種猜測:測試

  • 執行後,能夠看到輸出內容,即說明第二個線程可以感知到第一個線程對共享變量flag的修改
  • 執行後,沒有任務內容,即說明第二個線程沒法感知到第一個線程對共享變量flag的修改

而後執行結果爲:線程

沒有任務的輸出內容,即證實了此時這樣狀況下第二個線程沒法感知到第一個線程對共享變量flag的修改的code

如今修改一下例子,即爲flag變量加上volatile關鍵字,即:blog

private volatile boolean flag;

而後再運行,此時結果爲:內存

此時就有內容輸出了,說明加上volatile關鍵字後,第二個線程能夠感知到第一個線程對共享變量flag的修改的,這就是上面概念中所說的volatile在多處理器開發中保證了共享變量的「可見性」。

二、原理

經過上面的例子證實了volatile在多處理器開發中保證了共享變量的「可見性」,那它是怎麼實現的呢?

這時就得介紹一下Java的內存模型了,首先看以下示意圖:

Java內存模型是如上面所示的:

共享變量存儲在主內存中,每一個線程都有一個私有的本地內存,本地內存保存了被該線程使用到的主內存的副本拷貝,線程對變量的全部操做都必須在本身的本地內存中進行,而不能直接讀寫主內存中的變量。

根據此理解,上述例子的在沒有加volatile時的狀況是這樣的:

第一個線程從主內存中獲取共享變量flag的值,此時值爲false,將該值放到本身的本地內存中,而後對變量進行修改,將值改成true,此時也只是將本地內存中flag的值改成了true,此時尚未將值同步到主內存中,而後第二線程也是將共享變量flag的值放到本身的本地內存中,而此時flag的值仍是爲false,因此就是一直沒有內容輸出了。

然而加上volatile關鍵字後,第一個線程對flag的修改會強制刷新到主內存中去,同時還會致使其餘線程中的本地內存的值會無效,須要從新到主內存獲取,這樣就保證了第一個線程對flag修改後,第二線程可以感知到。

三、注意點

volatile是輕量級的synchronized,可是它是不可以代替synchronized的,由於volatile只能保證原子性操做的安全,對於複合操做,volatile是不能保證線程安全的。

例如:

package com.swnote.java;

/**
 * 複合操做例子
 *
 * @author lzj
 * @date [2019-04-27]
 */
public class StatisticTest {
    private volatile int num = 0;

    public static void main(String[] args) {
        StatisticTest test = new StatisticTest();
        test.statistic();
    }

    public void statistic() {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                num++;
            }).start();
        }

        System.out.println("num = " + num);
    }
}

指望的運行結果是20,但是幾乎每次運行結果都是不同的,例若有的結果爲:

這是由於num++這個操做不是原子性的,因此即便使用了volatile關鍵字,也是不能保證安全的。

關注我

以你最方便的方式關注我:
微信公衆號:

相關文章
相關標籤/搜索