單例模式討論

在之前的博文中曾經介紹過單例模式:

https://blog.csdn.net/timchen525/article/details/78244101

這裏重新討論們下單例模式,並且引入了一個基於枚舉的單例模式。

分析如下代碼:
**
 * 懶漢模式
 */
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }

    // 單例對象
    private static SingletonExample instance = null;

    // 靜態的工廠方法
    public static SingletonExample getInstance() {
        if (null == instance) {
            instance = new SingletonExample();
        }
        return instance;
    }
}

上述代碼也叫作懶漢模式,即只在第一次被調用的時候才初始化,不是線程安全的。不安全的原因:當兩個線程同時運行到if(null == instance)則,同時進行instance = new SingleExample();即被實例化兩次。
改進添加同步操作(synchronized):
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }

    // 單例對象
    private static SingletonExample instance = null;

    // 靜態的工廠方法
    public static synchronized SingletonExample getInstance() {
        if (null == instance) {
            instance = new SingletonExample();
        }
        return instance;
    }
}
上述添加了synchronized使得該方法同一時刻只能被一個線程訪問。因此,這種改進後的代碼是線程安全的。
進一步基於synchronized進行改進:
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }

    // 單例對象
    private static SingletonExample instance = null;

    // 靜態的工廠方法
    public static SingletonExample getInstance() {
        if (null == instance) { // B
            synchronized (SingletonExample.class) {
                if (null == instance) { // 雙重檢測機制
                    instance = new SingletonExample();  // A-3
                }
            }
        }
        return instance;
    }
}
分析:上述代碼將同步synchronized移到判null之後,此時,可能有兩個線程同時要去獲取synchronized(SingletonExample.class),因此,在內部裏面再次判斷一次。這樣寫的好處,之後,null != instance可以直接return,而不用進行同步synchronized操作。
注意注意:上述不是線程安全的。
instance = new SingletonExample()操作需要進行如下操作:
1)memory = allocate()分配對象的內存空間
2)ctorInstance()初始化對象
3)instance = memory設置instance的指向剛分配的內存
比如如下情況:JVM和CPU優化,發生了指令重排
1)memory = allocate()分配對象的內存空間
3)instance = memory設置instance的指向剛分配的內存
2)ctorInstance()初始化對象
比如:兩個線程,線程B執行到if(instance == null) 此時,線程B執行到instance=new SingletonExample();而new的操作被CPU指令重拍,比如到3),此時,instance == null判斷不成立,變返回instance產生錯誤。
再次改進上述線程不安全的單例(添加volatile)(線程安全寫法,推薦):
volatile + 雙重檢測機制 = 實現線程安全的單例模式
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }
    // 單例對象
    private volatile static SingletonExample instance = null;
    // 靜態的工廠方法
    public static SingletonExample getInstance() {
        if (null == instance) {
            synchronized (SingletonExample.class) {
                if (null == instance) {
                    instance = new SingletonExample();
                }
            }
        }
        return instance;
    }
}
改進成餓漢模式(線程安全的)
即單例模式在類加載時進行創建:
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }
    // 單例對象
    private static SingletonExample instance = new SingletonExample();

    // 靜態的工廠方法
    public static SingletonExample getInstance() {
        return instance;
    }
}
餓漢模式是線程安全的,但是可能會引起程序初始化加載變慢,如果程序沒有使用,則會造成資源浪費。
枚舉模式實現線程安全(最推薦,線程安全的):
public class SingletonExample {

    // 私有構造函數
    private SingletonExample() {
    }

    public static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample singletonExample;

        // JVM保證這個方法絕對只調用一次
        Singleton() {
            singletonExample = new SingletonExample();
        }

        public SingletonExample getInstance() {
            return singletonExample;
        }
    }
}
JVM保證構造函數Single()被調用一次,因此是線程安全的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章