Java併發(七):雙重檢驗鎖定DCL

雙重檢查鎖定(Double Check Lock,DCL)html

一、懶漢式單例模式,沒法保證線程安全:安全

    public class Singleton {
        private static Singleton singleton;

        private Singleton() {
        }

        public static Singleton getInstance() {
            if (singleton == null) {// 多個線程同時執行到此,會生成多個Singleton實例
                singleton = new Singleton();
            }

            return singleton;
        }
    }

二、同步處理,synchronized就會致使這個方法比較低效:多線程

    public class Singleton {
        private static Singleton singleton;

        private Singleton() {}

        public static synchronized Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }

            return singleton;
        }
    }

三、雙重檢查 DCL:併發

    public class Singleton {
        private static Singleton singleton;
        Integer a;

        private Singleton(){}

        public static Singleton getInstance(){
            if(singleton == null){                              // 1 只有singleton==null時才加鎖,性能好
                synchronized (Singleton.class){                 // 2
                    if(singleton == null){                      // 3
                        singleton = new Singleton();            // 4
                    }
                }
            }
            return singleton;
        }
    }

可是,仍然有問題!!post

建立對象過程:性能

(1)分配內存空間優化

(2)初始化對象url

(3)將內存空間的地址賦值給對應的引用spa

(2)(3)會被處理器優化,發生重排序線程

舉例:

A線程singleton = new Singleton()發生重排序,將分配的內存空間引用賦值給了靜態屬性singleton(即singleton != null),而對象還未初始化(即Integer a == null);

B線程此時調用getInstance()方法,由於singleton != null,直接返回singleton。當B線程使用singleton的a屬性時就會空指針。

分析:

問題在於singleton = new Singleton()的重排序

(1)不容許初始化階段步驟2 、3發生重排序。

(2)容許初始化階段步驟2 、3發生重排序,可是不容許其餘線程「看到」這個重排序。

解決:

一、利用volatile限制重排序

    public class Singleton {
        private volatile static Singleton singleton;// 經過volatile關鍵字來確保安全

        private Singleton(){}

        public static Singleton getInstance(){
            if(singleton == null){
                synchronized (Singleton.class){
                    if(singleton == null){
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

(1)分配內存空間

(2)初始化對象

(3)將內存空間的地址賦值給對應的引用

第(3)步 volatile修飾的變量singleton的寫入操做,經過內存屏障限制的重排序 參考:Java併發(六):volatile的實現原理

二、利用類初始化

JVM會保證一個類的類構造器在多線程環境中被正確的加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的類構造器,其餘線程都須要阻塞等待,直到活動線程執行方法完畢。

特別須要注意的是,在這種情形下,其餘線程雖然會被阻塞,但若是執行初始化的那條線程退出後,其餘線程在喚醒以後不會再次進入/執行初始化,由於在同一個類加載器下,一個類型只會被初始化一次

    public class Singleton {
        private static class SingletonHolder{
            public static Singleton singleton = new Singleton();
        }
        
        public static Singleton getInstance(){
            return SingletonHolder.singleton;
        }
    }

 

 

 

參考資料:

【死磕Java併發】—–Java內存模型之從JMM角度分析DCL

相關文章
相關標籤/搜索