我就站在你面前,你卻視而不見!

在上一篇文章一男子給對象轉賬5000元,居然又退還了!中,我們學習了並發三大特性之一的原子性,並對原子性問題進行分析。

這篇文章我們就一起來了解下可見性:

可見性

首先看下可見性的概念:

可見性就是指某一個線程修改了共享變量的值時,其他線程能夠立即得知這個修改

什麼?難道變量被修改了,線程不應該馬上讀取到的嗎?為什麼和我認知的不一樣呢?

好的,那麼接下來讓我們帶着問題,一起來搞懂可見性問題。

可見性問題

可見性問題的元兇就是 CPU 緩存,都怪 CPU 為程序性能優化做的努力,搞出這麼多幺蛾子。

關於 CPU 緩存可以閱讀:原來 CPU 為程序性能優化做了這麼多

首先在單核 CPU 上,是不存在可見性問題的,因為所有的線程都在一個 CPU 上執行,所有的線程都是操作同一 CPU 緩存,某一個線程修改了共享變量的值,另外的線程也可以馬上讀取到,因此是可見的。

單核CPU

如上圖所示,Thread-0Thread-1 都是在一個 CPU 緩存上進行操作,所以 Thread-0 修改了變量 flag 的值後,Thread-1 再去訪問變量 flag,得到的一定是最新的 flag 值。

然而在多核 CPU 上,由於每個 CPU 都有自己的緩存,當多個不同線程運行在不同的 CPU 上時,這些線程操作的 CPU 緩存也是不同的,因此某一個線程對共享變量進行修改時,另外的線程讀取到的不一定是最新值,也就不具有可見性了。

多核CPU

如上圖所示,Thread-0 是在 CPU-0 上的緩存進行操作,Thread-1 是在 CPU-1 上的緩存進行操作,所以 Thread-0 修改了變量 flag 的值後,Thread-1 再去訪問變量 flag,得到的不一定是最新的 flag 值,因此 Thread-0 對共享變量 flag 的修改對 Thread-1 是不可見的。

下面用一個例子來看下可見性問題,創建一個 VisibilityTest 類,實現 Runnable 接口,在 run() 方法中判斷 flag 是否為 true,若為 true 則進行打印操作,主方法中啟動一個線程 thread,主線程等待 0.5 秒後,將 flag 的值設為 true

public class VisibilityDemo {
    private static class VisibilityTest implements Runnable {
        private boolean flag = false;
        @Override
        public void run() {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + ":" + flag);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest visibilityTest = new VisibilityTest();
        Thread thread = new Thread(visibilityTest);
        thread.start();
        // 等待線程啟動
        Thread.sleep(500);
        // 更新 flag 為 true
        visibilityTest.flag = true;
        System.out.println(Thread.currentThread().getName() + ":" + visibilityTest.flag);
    }
}

發現輸出的結果為:main:true,和我們想像的不太一樣,按道理 thread 應該會持續打印出 Thread-0:true 的,但是並非如此,這也就驗證了我們剛才講的可見性問題。

那麼如何解決可見性問題呢?

可以採用同步的方式去解決或者使用 volatile 關鍵字也可以保證可見性。

關於 volatile 相關原理可以閱讀:你真的了解 volatile 關鍵字嗎?

總結

本文學習了線程安全三大特性之中的可見性,另外 CPU 緩存在提高程序性能的同時也帶來了可見性問題,只有我們理解了可見性的原理,才更容易去診斷並發編程中的 BUG。

參考

《Java並發編程實戰》

《深入理解Java虛擬機:JVM高級特性與最佳實踐》

《實戰Java高並發程序設計》

《Java多線程編程核心技術》

Java並發編程實戰

Tags: