Java 併發 volatile學習筆記

volatile關鍵字是Java中提供的另一種解決可見性和有序性問題的方案。
對volatile變量的單次讀/寫操作可以保證原子性的。
但是並不能保證i++這種操作的原子性,因爲本質上i++是讀、寫兩次操作。

在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制

當且僅當滿足以下所有條件時,才應該使用volatile變量:

對變量的寫操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
該變量不會與其他變量一起納入不變性條件中。
在訪問變量時不需要加鎖。

volatile 關鍵字,使一個變量在多個線程間可見
A B線程都用到一個變量,java默認使A線程中保留一份copy,這樣如果B線程修改了該變量,則A線程未必知道

可以通過下圖來表示線程的存儲:
在這裏插入圖片描述

使用volatile關鍵字,會讓所有線程都會讀到變量的修改值
volatile 並不能保證多個線程共同修改running變量時所帶來的不一致問題,也就是說volatile不能代替synchonized

public class T6 {
    /*volatile*/ boolean running = true;
    public void m()
    {
        System.out.println("start");
        while(running)
        {
            //加入sleep 可能抽出時間來更新緩衝區中的值
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        }
        System.out.println("end");

    }

    public static void main(String[] args) {
        T6 t6 = new T6();
        new Thread(()->t6.m()).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t6.running = false;
    }
}

程序運行結果:
在這裏插入圖片描述
可以看到線程1陷入了死循環,也就是說線程1並沒有收到主線程更新running值的操作,這是因爲線程1一直在執行while循環,雖然主線程向內存寫入了running的值,但是線程1沒有時間去讀內存中running的值,所以會一直陷入死循環。
當在源程序的基礎上在while循環中加入睡眠1秒鐘時的運行結果:
在這裏插入圖片描述
可以看到因爲睡眠了1秒鐘,線程1趁機去內存中更新了running的值。
在源程序的基礎上在running上添加volatile關鍵字的運行結果:
在這裏插入圖片描述
由於加上了volatile關鍵字,使得主線程更新running值的同時,線程1會收到通知,去更新自己緩衝區中running變量的值,於是while循環就退出了。
volatile 並不能保證多個線程共同修改running變量時所帶來的不一致問題,也就是說volatile不能代替synchonized。
synchnized 既可以保障原子性和可見性,volatile只能保障可見性

public class T7 {
    volatile int count = 0;
    /*synchronized*/ void m()
    {
        for (int i=0;i<10000;i++) count++;
    }

    public static void main(String[] args) {
        T7 t7 = new T7();
        List<Thread> threadList= new ArrayList();
        for (int i=0;i<10;i++)
        {
            threadList.add(new Thread(()->t7.m(),"thread"+i));
        }
        //啓動線程
        threadList.forEach((o)->o.start());
        //等待線程結束
        threadList.forEach((o)->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t7.count);
    }
}

程序運行結果:
在這裏插入圖片描述
可以看到count的值比正確值100000差了很多,因爲是多個線程同時執行count++操作,並且count++操作是一個讀->寫的過程,多個線程同時進行讀->寫的操作就會出現加的值少的問題。
給m方法加上synchronized關鍵字後的運行結果:
在這裏插入圖片描述
因爲給方法加了鎖,所以使線程線性執行,所以不會出現問題。
解決同樣問題的更高效的方法是使用AtomXXXX類
AtomXXXX類本身方法都是原子性的,但不能保證多個方法連續調用是原子性的。

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