對java中volatile關鍵字的理解

volatile關鍵字的做用:保證變量的可見性java

必定要謹記 volatile 關鍵字在 Java 代碼中僅僅保證這個變量是可見的:它不保證原子性。在那些非原子且可由多個線程訪問的易變操做中,必定不可以依賴於 volatile 的同步機制。相反,要使用 java.util.concurrent 包的同步語句、鎖類和原子類。它們在設計上可以保證程序是線程安全的。c++

volatile做用的具體描述git

Java如何保證可見性

Java提供了volatile關鍵字來保證可見性。當使用volatile修飾某個變量時,它會保證對該變量的修改會當即被更新到內存中,而且將其它緩存中對該變量的緩存設置成無效,所以其它線程須要讀取該值時必須從主內存中讀取,從而獲得最新的值。github

                                                                                            引自- wangzzu的博客文章緩存

上面這段話能十分清楚的說明這個變量的用處了吧。下面配合代碼更加準確的驗證這個問題。安全

 代碼引自-- 阿里工程師oldratlee的githubbash

Demo類com.oldratlee.fucking.concurrency.NoPublishDemoapp

Demo說明

主線程中設置屬性stoptrue,以控制在main啓動的任務線程退出。dom

問題說明

在主線程屬性stoptrue後,但任務線程持續運行,即任務線程中一直沒有讀到新值。ide

出現上述問題說明的代碼:

public class NoPublishDemo {
    boolean stop = false;

    public static void main(String[] args) {
        // LoadMaker.makeLoad();

        NoPublishDemo demo = new NoPublishDemo();

        Thread thread = new Thread(demo.getConcurrencyCheckTask());
        thread.start();

        Utils.sleep(1000);
        System.out.println("Set stop to true in main!");
        demo.stop = true;
        System.out.println("Exit main.");
    }

    ConcurrencyCheckTask getConcurrencyCheckTask() {
        return new ConcurrencyCheckTask();
    }

    private class ConcurrencyCheckTask implements Runnable {
        @Override
        public void run() {
            System.out.println("ConcurrencyCheckTask started!");
            // 若是主線中stop的值可見,則循環會退出。
            // 在個人開發機上,幾乎必現循環不退出!(簡單安全的解法:在running屬性上加上volatile)
            while (!stop) {
            }
            System.out.println("ConcurrencyCheckTask stopped!");
        }
    }
}

例子中主線程的stop變量沒有作任何的顯示定義volatile。

當咱們執行後的打印結果爲:

ConcurrencyCheckTask started!
Set stop to true in main!
Exit main.

並無打印

ConcurrencyCheckTask stopped!

而且程序尚未結束。

接下來咱們對stop變量添加volatile

volatile boolean stop = false;

程序完美結束。也印證了上文描述中的內容。

組合狀態讀到無效組合

--此時使用volatile則沒法解決同時修改兩個值,數據仍是返回正確的問題。

意思是同時對兩個值修改,可是實際返回確實錯誤的。

程序設計時,會須要多個狀態記錄(狀態能夠是個POJO對象或是int等等)。常看到多狀態讀寫沒有同步的代碼,而且寫的同窗會很天然地就忽略了線程安全的問題。

無效組合 是指 歷來沒有設置過的組合。

Demo說明

主線程修改多個狀態,爲了方便檢查,每次寫入有個固定的關係:第2個狀態是第1個狀態值的2倍。在任務線程中讀取多個狀態。
Demo類com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo

問題說明

任務線程中讀到了 第2個狀態不是第1個狀態值2倍的值,便是無效值。

快速運行

mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo

實際測試代碼

public class InvalidCombinationStateDemo {
    public static void main(String[] args) {
        CombinationStatTask task = new CombinationStatTask();
        Thread thread = new Thread(task);
        thread.start();

        Random random = new Random();
        while (true) {
            int rand = random.nextInt(1000);
            task.state1 = rand;
            task.state2 = rand * 2;
        }
    }

    private static class CombinationStatTask implements Runnable {
        // 對於組合狀態,加 volatile 不能解決問題
        volatile int state1;
        volatile int state2;

        @Override
        public void run() {
            int c = 0;
            for (long i = 0; ; i++) {
                int i1 = state1;
                int i2 = state2;
                if (i1 * 2 != i2) {
                    c++;
                    System.err.printf("Fuck! Got invalid CombinationStat!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n",
                            i + 1, c, (float) c / (i + 1) * 100, i1, i2);
                } else {
                    // 若是去掉這個輸出,則在個人開發機上,發生無效組合的機率由 ~5% 降到 ~0.1%
                    System.out.printf("Emm... %s|%s\n", i1, i2);
                }
            }
        }
    }

}

long變量讀到無效值

無效值 是指 歷來沒有設置過的值。

long變量讀寫不是原子的,會分爲2次4字節操做。
Demo類com.oldratlee.fucking.concurrency.InvalidLongDemo

Demo說明

主線程修改long變量,爲了方便檢查,每次寫入的long值的高4字節和低4字節是同樣的。在任務線程中讀取long變量。

問題說明

任務線程中讀到了高4字節和低4字節不同的long變量,便是無效值。

快速運行

mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidLongDemo

DEMO:

public class InvalidLongDemo {
    long count = 0;

    public static void main(String[] args) {
        // LoadMaker.makeLoad();

        InvalidLongDemo demo = new InvalidLongDemo();

        Thread thread = new Thread(demo.getConcurrencyCheckTask());
        thread.start();

        for (int i = 0; ; i++) {
            long l = i;
            demo.count = l << 32 | l;
        }
    }

    ConcurrencyCheckTask getConcurrencyCheckTask() {
        return new ConcurrencyCheckTask();
    }

    private class ConcurrencyCheckTask implements Runnable {
        @Override
        public void run() {
            int c = 0;
            for (int i = 0; ; i++) {
                long l = count;
                long high = l >>> 32;
                long low = l & 0xFFFFFFFFL;
                if (high != low) {
                    c++;
                    System.err.printf("Fuck! Got invalid long!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n",
                            i + 1, c, (float) c / (i + 1) * 100, high, low);
                } else {
                    // 若是去掉這個輸出,則在個人開發機上沒有觀察到invalid long
                    System.out.printf("Emm... %s|%s\n", high, low);
                }
            }
        }
    }

}

對於單個變量的值修改 給變量添加volatile也是能夠解決此問題的。

 

十分感謝

Matt's Blog 

李鼎(哲良)

相關文章
相關標籤/搜索