volatile實踐,必須弄懂它!

 

這篇文章算是重構了,知識這種東西,理解的不深,用的就猶豫

 

爲什麼會線程不安全?

計算機在執行程序時,每條指令都是在CPU中執行的,而執行指令過程中會涉及到數據的讀取和寫入。程序運行過程中的臨時數據是存放在主存(物理內存)當中的,由於CPU執行速度很快,而從內存讀取數據和向內存寫入數據的過程跟CPU執行指令的速度比起來要慢的多,因此如果任何時候對數據的操作都要通過和內存的交互來進行,會大大降低指令執行的速度。

爲了處理這個問題,在CPU裏面就有了高速緩存(Cache)的概念。當程序在運行過程中,會將運算需要的數據從主內存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。這個高速緩存就是工作內存

規則:

Java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。

 

volatile如何解決這個問題?

對於多線程來說,注意三個特性:1.原子性,2.有序性,3.可見性,這三個特性保證了多線程的安全,這裏不講述

volatile保證了1.可見性;2.有序性; 注意不保證原子性;

可見性

含義:在多線程環境下,某個共享變量如果被其中一個線程給修改了,其他線程能夠立即知道這個共享變量已經被修改了,當其他線程要讀取這個變量的時候,最終會去主內存中讀取,即讀取的是最新的值

如int a = b+1,一個線程執行此操作,另一個線程執行讀,那麼只要a是+1後,讀取的時候一定是最新的值

如配置中心修改某值,若用volatile修飾此共享變量則會立即生效

有序性

當我們把代碼寫好之後,虛擬機不一定會按照我們寫的代碼的順序來執行

虛擬機在進行代碼編譯優化的時候,對於那些改變順序之後不會對最終變量的值造成影響的代碼,是有可能將他們進行重排序的

重排序可能會造成線程安全的問題

如果共享變量被volatile修飾,虛擬機會保證這個變量之前的代碼一定會比它先執行,而之後的代碼一定會比它慢執行。不論之前還是之後,它都無法保證重排序,只能保證當前修飾的變量前後是禁止重排序的

 

雖然volatile能夠保證其他線程立即可見,可見不代表  現有的值一定要重新獲取,不代表它是線程安全的,因爲多個線程修改共享變量就會有問題

volatile線程不安全的栗子

栗子:

int a = a+1

執行過程爲:

  1. 從內存中讀取a的值。
  2. 進行a = a + 1這個運算
  3. 把a的值寫回到內存中

如果是兩個線程同時讀取到b爲0,分別+1,寫回主內存的值就是1,線程不安全。

栗子:起100個線程,循環100次

public static volatile int t = 0;
public static void main(String[] args) throws InterruptedException {
    for (int i=0;i<100;i++){
        for (int j=0;j<100;j++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    t = t + 1;
                }
            }).start();
        }
        //打印t的值
        System.out.println(t);
        Thread.sleep(1000);
        t = 0;
    }
}

98

98

99

98

99

99

99

下面用AtomicInteger來保證線程安全 

    public static AtomicInteger t = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<100;i++){
            for (int j=0;j<100;j++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int x = t.incrementAndGet();
                        System.out.println("t after "+x);
                    }
                }).start();
            }
            //打印t的值
            Thread.sleep(2000);//每隔2s置0重計數
            System.out.println(t);
            t = new AtomicInteger(0);
        }
    }

執行結果大家想想看應該是什麼樣的,然後自己運行一下

所以volatile在滿足以下兩個條件可以保證變量的線程安全問題

1.運算結果不依賴於變量的當前值,或者能夠確保只有單一的線程修改變量的值

2.變量不需要與其他狀態變量共同參與不變約束

所以可以在操作動作上    可以用JUC包,synchronized,Lock來控制原子性,保證線程安全 

 

參考:https://www.cnblogs.com/kubidemanong/p/9505944.html

 

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