【Java】之 volatile



一、簡介


happen before 是時鐘順序的先後,並不能保證線程交互的可見性。

即:存在某線程對副本操作,但對於其他線程都是不可見的。

可見性:指某線程修改共享變量的指針對其他線程來說都是可見的,它反映的是指令執行的實時透明度

每個線程都有獨佔的內存區域,如操作棧、本地變量表等
線程本地內存保存了引用變量在堆內存中的副本,線程對變量的所有操作都在本地內存區域中進行,執行結束後再同步到堆內存中。

線程執行或線程切換都是納秒級的。

volatile: 揮發,不穩定。

當使用 volatile修飾變量時,意味着任何對此變量的操作都會在內存中進行,不會產生副本,以保證共享變量的可見性,局部阻止了指令重排的發生。

volatile解決的是多線程共享變量的可見性問題,類似於 synchronized,但不具備sychronized的互斥性



二、案例



(1)雙檢查鎖(Double-checked Locking)

如下代碼:

class LazyInitDemo {
    
    private static TransactionService service = null;
    
    public static TransactionService getTransactionService() {
        
        if (service == null) {
            // 或者 TransactionService.class
            synchronized (LazyInitDemo.class) {
                if (service == null) {
                    service == new TransactionService();
                }
            }
        }
        
        return service;
    }
}

調用getTransactionService()可能會得到初始化未完成的對象

原因:與 JVM 的編譯優化有關
線程1 執行new TransactionService(), 構造方法還未被調用,編譯器僅僅爲該對象分配了內存空間並設爲默認值
線程2 調用getTransactionService(),由於 service != null,但是此時service對象還沒有被賦予真正有效的值,從而無法取到正確的 service 單例對象。

解決方法:加上volatile
private static volatile TransactionService service = null;


(2)volatile 不具互斥性

public class VolatileNotAtomic {
    private static volatile long count = 0L;
    private static final int NUMBER = 10000;

    public static void main(String[] args) {
        
        Thread subtractThread = new SubtractThread();
        subtractThread.start();
        
        for (int i = 0; i < NUMBER; ++i) {
            count ++;
        }
        
        // 等待減法線程結束
        while (subtractThread.isAlive()){}

        System.out.println("count 最後的值: " + count);
    }
    
    private static class SubtractThread extends Thread {
        
        @Override
        public void run() {
            for (int i = 0; i < NUMBER; ++i) {
                count --;
            }
        }
    }
}

結果基本不爲0, 因爲--++並不是原子操作。

字節碼如下:

// 1. 讀取 count 並壓入操作棧頂
GETSTATIC count: I

// 2. 常量 1 壓入操作棧頂
ICONST_1

// 3. 取出最頂部兩個元素進行相加
IADD

// 4. 將剛纔得到的和賦值給 count
PUTSTATIC count: I

解決方案:針對--++,可以加鎖,如sychronized



三、實際場景


  1. 能實現 count++ 原子操作的其他類有:AtomicLongLongAdder

JDK8 推薦使用 LongAdder類,它比 AtomicLong性能更好,有效地減少了樂觀鎖的重試次數

  1. 一讀多寫的併發場景,使用volatile修飾變量則非常合適

最典型的應用:CopyOnWriteArrayList

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