設計模式之單例模式

1.定義

單例模式是Java中比較常見的創建型設計模式,他的核心是確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。

如何確保一個類在任何情況下都絕對只有一個實例?是單例模式設計的主要實現方向。下面介紹下單例模式的主要實現方法

2.餓漢式

/**
 * 餓漢式
 * 在類加載時直接實例化單例
 * 缺點,類加載時就創建實例,浪費空間
 */
public class HungryMan {
    /**
     * 類加載時創建初始化
     */
    private static final HungryMan INSTANCE = new HungryMan();

    /**
     * 私有化構造方法
     */
    private HungryMan(){}

    /**
     * 提供全局訪問點
     * @return 單例對象
     */
    public static HungryMan getInstance() {
        return INSTANCE;
    }
}

3.懶漢式

/**
 * 懶漢式 未加鎖 存在線程安全問題
 * 在調用時才實例化單例
 */
public class LazyMan {
    /**
     * 類加載時不初始化實例
     */
    private static LazyMan INSTANCE;

    /**
     * 私有化構造方法
     */
    private LazyMan() {
    }

    public static synchronized LazyMan getInstance() {
        //這裏需要辦法保證只有一個實例
        if (INSTANCE == null)
            INSTANCE = new LazyMan();
        return INSTANCE;
    }
}

改進版

/**
 * 懶漢式 雙重檢查鎖單例
 * 爲了保證單例的線程安全,使用雙重加鎖的方式
 * 問題:加鎖所造成的性能問題
 */
public class LazyMan2 {
    /**
     * 類加載時不初始化實例
     * <p>
     * volatile 可以防止指令重排序問題
     */
    private volatile static LazyMan2 INSTANCE;

    /**
     * 私有化構造方法
     */
    private LazyMan2() {
    }

    /**
     * 等到調用時初始化單例實例
     * 如果沒有 synchronized關鍵字,容易出現線程安全問題,因此需要添加synchronized進行同步
     *
     * @return 單例對象
     */
    public static LazyMan2 getInstance() {
        // 1.如果INSTANCE不爲null,則不需要獲取鎖,提高性能
        if (INSTANCE == null)
            synchronized (LazyMan2.class) {
                // 2.爲了避免多線程解鎖後重復創建對象
                if (INSTANCE == null)
                    /*
                     * CPU執行時會轉換成JVM指令執行
                     *
                     * 1.分配內存給這個對象
                     * 2.初始化對象
                     * 3.將初始化後的對象和內存地址建立關聯,賦值
                     * 第二步和第三步可能調換順序(賦值在創建對象之前),線程B在線程A賦值完時判斷instance就不爲null了,此時B拿到的將是一個沒有初始化完成的半成品。
                     * 所以會在單例對象添加volatile修飾
                     */
                    INSTANCE = new LazyMan2();
            }
        return INSTANCE;
    }
}

4.註冊式單例模式

4.1 容器式單例

/**
 * 容器式單例
 *
 * 容器式單例都屬於註冊式單例模式,其核心思想是:
 * 在使用時,先去容器中查找,如果找到了,就將查出來的對象返回
 * 否則,實例化,然後轉載到容器中,最後將實例化的對象返回
 */
public class ContainerSingleton {
    /**
     * 單例容器
     * 存在線程安全問題,因此使用ConcurrentHashMap
     */
    private static final Map<String, Object> ioc = new ConcurrentHashMap<>();

    /**
     * 私有化構造函數
     */
    private ContainerSingleton() {
    }

    /**
     * 容器式單例模式
     *
     * @param key 獲取單例的key
     * @return 單例對象
     */
    public static Object getBean(String key) {
        if (ioc.containsKey(key)) {//如果有就取出返回
            return ioc.get(key);
        }

        //如果沒有,新建-裝載-返回
        try {
            Object instance = Class.forName(key).newInstance();
            ioc.put(key, instance);
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //裝載異常, 返回空
        return null;
    }
}

3.2 枚舉式單例(強烈推薦)

/**
 * 枚舉單例模式
 * 屬於裝載類單例模式
 *
 * 在調用時,先查詢容器中是否有此對象的實例,有就取出直接返回,否則新建一個實例並且將其裝載到容器中
 * 體現在Enum.valueOf((Class)cl, name);這個方法上
 */
public enum EnumSingleton {
    INSTANCE;

    /**
     * 用來擴展的對象
     */
    private Object object;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    /**
     * 提供全局訪問點
     *
     * @return 單例對象
     */
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

5.簡單總結

  • 私有化構造器
  • 保證線程安全
  • 延遲加載
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章