線程同步 ——volatile

上一篇咱們簡單的使用了synchoronized實現線程同步,接下來簡單的使用volatile實現線程同步。java

public class ThreadCount {
    
    //若是去掉volatile就會出現錯誤
    private static volatile int total;

    public static void main(String[] args) {

        Counter counter1 = new Counter(10);
        Counter counter2 = new Counter(20);
        Counter counter3 = new Counter(5);
        Counter counter4 = new Counter(10);
        Counter counter5 = new Counter(10);
        Counter counter6 = new Counter(20);
       
        counter1.start();
        counter2.start();
        counter3.start();
        counter4.start();
        counter5.start();
        counter6.start();   
    }

     static class Counter extends Thread {
        private int i;

        Counter(int i) {
            this.i = i;

        }

        @Override
        public void run() {
            synchronized (this) {
                total += i;
                System.out.println("結果爲: " + total);
            }
        }
    }

}
//程序運行結果
結果爲: 10
結果爲: 30
結果爲: 35
結果爲: 45
結果爲: 55
結果爲: 75
//去掉volatile程序運行結果
結果爲: 30
結果爲: 45
結果爲: 35
結果爲: 30
結果爲: 75
結果爲: 55

一個關鍵字volatile可讓程序即可以上程序正確的執行,volatile有什麼神通呢,接下來讓我娓娓道來:緩存

在jmm(java內存模型)中,線程共享主內存,其次每一個線程有本身私有的緩存,正常狀況下線程更新主內存數據的時候得先更新本身的緩存的數據,而後更新主內的數據,而使用volatile後,線程直接更新住內存數據,這樣就保證了當前線程更新共享數據後對別的線程當即得知這個修改。多線程

Java內存模型的抽象示意圖以下:ide

                              

對於i++下i被volatile修飾,多線程下能獲得正確的結果嗎?請看下面的例子:this

public class VolatileTest {
    public static volatile int race = 0;

    public static void increase(){
        race++;
    }

    private static final int THREAD_COUNT = 200;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; i++){
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    for(int i = 0; i < 2000; i++){
                        increase();
                    }
                }
            });

            threads[i].start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
            System.out.println(race);

        }
    }
}
//執行結果
393715
393715
393715
393715
393715
393715

你會發現執行結果小於咱們預期400000,其實不管執行多少次都會小於400000,這個是爲何呢?反編譯這段代碼發現increase()方法產生四條指令,1:getstiatic   2:iconst_1  3: iadd   4:putstatic,當getstatic指令把race的值渠道操做棧頂時,volatile只保證了race在此時準確,可是執行iconst_一、iadd指令時可能其餘的線程把race的值增大,而這個時候棧頂已經變爲實效的數據,因此putstatic可能會被較小的race放會到主內存,其實還有編譯出來只有一條字節碼指令,也不意味着這條指令在機器執行時是一個原子操做。spa

因此說volatile只能保證可見行,不能保證原子性,咱們仍然須要加鎖來保證原子性 (使用synchoronized或java.util.concurrent中的原子類)線程

synchoronized如何保證原子性:code

Java內存模型提供lock和unlock操做來保證原子性,儘管虛擬機沒有把lock和unlock直接開放給用戶使用,可是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱式地使用這兩個操做,這兩個字節嗎指令反映到java代碼中就是同步塊synchoronized關鍵字。內存

相關文章
相關標籤/搜索