設計模式筆記-單例(Singleton)

簡介

  • 分類:創建型
  • 來源:Gang Of Four
  • 意圖:保證類只有一個實例,並提供全局的訪問入口

單例

  • 單例模式限制了類的實例化(私有構造器),保證虛擬機中一個類(一份字節碼,一個類文件可以被不同類加載器多次加載,不屬於同一份字節碼,例如tomcat中部署多個相同的應用)只有一個實例。
  • 單例實現類必須提供一個獲取實例對象的全局訪問入口

單例實現

多種實現單例的方式,但都具備以下概念。

  • 通過私有構造器限制類的初始化
  • 通過私有靜態變量保證只有一個實例
  • 通過公有靜態方法提供實例的全局訪問接口
  1. 餓漢模式
  2. 餓漢模式-靜態塊
  3. 懶漢模式-非線程安全
  4. 懶漢模式-線程安全
  5. 雙檢查鎖模式-DCL

餓漢模式

package design.structural.singleton;

/**
 * 餓漢模式
 * 優點:
 *   實現簡單
 * 缺點:
 *   不能延時加載,即使單例不會被用到頁會被初始化
 *   不能進行異常處理
 *
 * @author faith.huan 2019-12-02 22:11
 */
public class EagerSingleton {
    /**
     * 立即加載方式 = 餓漢模式
     */
    private static EagerSingleton singleton = new EagerSingleton();

    /**
     * 通過私有構造器,限制類的實例化
     */
    private EagerSingleton(){}

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static EagerSingleton getInstance(){
        return singleton;
    }

}

餓漢模式-靜態塊

針對餓漢模式無法處理異常缺陷的優化版本,採用靜態塊初始化實例

package design.structural.singleton;

/**
 * 餓漢模式-靜態塊
 * 優點:
 *   實現簡單
 * 缺點:
 *   不能延時加載,即使單例不會被用到頁會被初始化
 *
 * @author faith.huan 2019-12-02 22:11
 */
public class EagerStaticBlockSingleton {
    /**
     * 立即加載方式 = 餓漢模式
     */
    private static EagerStaticBlockSingleton singleton ;

    static {
        try {
            singleton = new EagerStaticBlockSingleton();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 通過私有構造器,限制類的實例化
     */
    private EagerStaticBlockSingleton(){}

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static EagerStaticBlockSingleton getInstance(){
        return singleton;
    }

}

懶漢模式-非線程安全

package design.structural.singleton;

/**
 * 懶漢模式-非線程安全
 * 優點
 *   延時初始化
 * 缺點
 *   線程不安全
 *
 * @author faith.huan 2019-12-02 22:33
 */
public class LazyThreadNotSafeSingleton {

    /**
     * 延時加載方式 = 懶漢模式
     */
    private static LazyThreadNotSafeSingleton singleton;

    /**
     * 通過私有構造器,限制類的實例化
     */
    private LazyThreadNotSafeSingleton(){}

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static LazyThreadNotSafeSingleton getInstance(){
        if(singleton==null){
            singleton = new LazyThreadNotSafeSingleton();
        }
        return singleton;
    }
}

懶漢模式-線程安全

解決線程安全最簡單的方式就是在靜態方法上加synchronized關鍵字

package design.structural.singleton;

/**
 * 懶漢模式-線程安全
 * 優點
 *   延時初始化
 * 缺點
 *   用於每次獲取對象都需要加鎖,性能較差
 *
 * @author faith.huan 2019-12-02 22:33
 */
public class LazyThreadSafeSingleton {

    /**
     * 延時加載方式 = 懶漢模式
     */
    private static LazyThreadSafeSingleton singleton;

    /**
     * 通過私有構造器,限制類的實例化
     */
    private LazyThreadSafeSingleton(){}

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static synchronized LazyThreadSafeSingleton getInstance(){
        if(singleton==null){
            singleton = new LazyThreadSafeSingleton();
        }
        return singleton;
    }
}

雙檢查鎖模式-DCL

針對懶漢模式-線程安全版本使用方法級的synchronize關鍵字來實現線程安全帶來的性能問題,採用Double Check Lock(DCL)方式來優化。

package design.structural.singleton;

/**
 * 雙檢查鎖模式-DCL
 * 優點
 *   延時初始化,線程安全
 * 缺點
 *   JDK1.5及以後版本
 *
 * @author faith.huan 2019-12-02 22:33
 */
public class DoubleCheckLockSingleton {

    /**
     * 延時加載方式 = 懶漢模式
     */
    private static volatile DoubleCheckLockSingleton singleton;

    /**
     * 通過私有構造器,限制類的實例化
     */
    private DoubleCheckLockSingleton() {
    }

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static DoubleCheckLockSingleton getInstance() {
        if (singleton == null) {
            synchronized (DoubleCheckLockSingleton.class) {
                if (singleton == null) {
                    singleton = new DoubleCheckLockSingleton();
                }
            }
        }
        return singleton;
    }
}

要點:

  • 靜態變量singleton需使用volatile關鍵字修飾
    代碼singleton = new DoubleCheckLockSingleton();會分解爲以下三行僞代碼
    1.memory = allocate(); // 1. 分配對象內存空間
    2.initInstance(memory); // 2. 初始化對象
    3.singleton =memory; // 3.設置singleton變量指向剛分配的內存地址
    在不加volatile修飾時,上面僞代碼中2、3步可能會發生重排序,也就是先指向內存地址(此時singleton==null已爲false),再初始化對象。
    多線程時序表
時間 線程A 線程B
T1 A1: 分配對象內存空間
T2 A3: 設置singleton指向剛分配的內存地址
T3 B1:判斷 singleton是否爲null
T4 B2: 由於singleton不爲null
線程B將訪問singleton對象
T5 A2:初始化對象
  • 此方案需要JDK1.5或更高版本
    從JDK1.5開始使用新的JSR-133內存模型規範,此規範增強了volatile的語義,此處使用的是禁止指令重排

靜態內部類

package design.structural.singleton;

/**
 * 靜態內部類
 * 比爾.普格
 *
 * @author faith.huan 2019-12-03 22:16
 */
public class BillPughSingleton {

    /**
     * 通過私有構造器,限制類的實例化
     */
    private BillPughSingleton() {
    }

    /**
     * 通過靜態內部類實現單例
     * 只有當getInstance方法調用時,SingletonHolder纔會被加載
     */
    private static class SingletonHolder {
        private static final BillPughSingleton SINGLETON = new BillPughSingleton();
    }

    /**
     * 通過公有靜態方法,提供全局訪問入口
     */
    public static BillPughSingleton getInstance() {
        return SingletonHolder.SINGLETON;
    }
}

枚舉

package design.structural.singleton;

/**
 * 枚舉模式
 *
 * @author faith.huan 2019-12-03 22:26
 */
public enum EnumSingleton {
    /**
     * 單例對象
     */
    SINGLETON;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章