單例模式

#學習記錄(剛剛學習之後進行了簡單的總結)

github地址 https://github.com/gaoruiqiang2017/singleton.git

單例模式的定義

單例模式(Singleton Pattern)是指確保一個類在任何情況
下都絕對只有一個實例,並提供一個全局訪問點。
隱藏其所有的構造方法。
屬於創建型模式。
確保任何情況下都絕對只有一個實例,如 ServletContext、ServletConfig、ApplicationContext

餓漢式單例

public class HungrySingleton {
    /*
     * 餓漢式單例
     * 它在類加載的時候就立即初始化,並且創建單例對象
     * 優點:沒有加任何的鎖,執行效率比較高,用戶體驗上來說比懶漢式更好
     * 缺點:類加載的時候就初始化,不管用或者不用,都佔着空間,浪費了內存
     *
     * 絕對線程安全,在線程還沒有出現之前就實例化了,不可能存在訪問安全問題
     * 餓漢式適用在單例對象較少的情況
     * */
    private static final HungrySingleton hungetSingleton = new HungrySingleton();

    //私有的無參構造
    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungetSingleton;
    }

}

還有種寫法

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungryStaticSinglrton;

    //餓漢式靜態塊單例
    static {
        hungryStaticSinglrton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getHungryStaticSinglrton() {
        return hungryStaticSinglrton;
    }
}

懶漢式單例

第一種寫法

public class LazySimpleSingleton {
    /*
     * 懶漢式單例
     * 在外部需要使用的時候才進行實例化
     * */
    private LazySimpleSingleton() {
    }

    //靜態塊,公共內存區域
    private static LazySimpleSingleton lazy = null;

    /*
     * 用synchronized 加鎖,在線程數量比較多情況下,如果CPU 分配壓力上升,會導致大批
     * 量線程出現阻塞,從而導致程序運行性能大幅下降。
     * */
    public synchronized static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }

}

第二種寫法

public class LazyDoubleCheckSingleton {

    private LazyDoubleCheckSingleton() {
    }

    //靜態塊,公共內存區域
    //volatile:指令重排序
    private volatile static LazyDoubleCheckSingleton lazy = null;
    
    //雙重檢查鎖
    public static LazyDoubleCheckSingleton getInstance() {
        if (lazy == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazy == null) {
                    lazy = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazy;
    }

}

第三種寫法

public class LazyInnerClassSingleton {


    private LazyInnerClassSingleton() {
        //反射可以破壞單例,需要在無參構造中加判斷
        if (LazyHolder.lazy != null) {
            throw new RuntimeException("不允許創建多個實例");
        }
    }

    //static是爲了使單例的空間共享,保證這個方法不會被重寫,重載
    public static final LazyInnerClassSingleton getInstance() {
        //返回結果之前,一定會先加載內部類
        return LazyHolder.lazy;
    }

    ///默認使用LazyInnerClassGeneral 的時候,會先初始化內部類
    //如果沒使用的話,內部類是不加載的
    private static class LazyHolder {
        private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
    }

    //反序列化時會破壞單例
    //反序列化將已經持久化的字節碼內容,轉換爲IO流
    //通過IO流的讀取,進而將讀取的內容轉換爲Java對象
    //在轉換過程中會重新創建對象new
    //重寫 readResolve() 方法可以解決這個問題,但是只不過是覆蓋了反序列化出來的對象
    //還是創建了倆次,發生在JVM層面,比較安全,但是內存依舊會有開銷
    //之前反序列化出來的對象會被GC回收
    private Object readResolve() {
        return LazyHolder.lazy;
    }

}

註冊式單例

將每一個實例都緩存到統一的容器中,使用唯一標識獲取實例

public enum EnumSingleton {
    INSTANCE;

    /*
     * 枚舉式單例,也是註冊時單例中的一中
     * 枚舉式單例在靜態代碼塊中就給INSTANCE 進行了賦值,是餓漢式單例的實現
     * 枚舉類型其實通過類名和Class 對象類找到一個唯一的枚舉對象。因此,枚舉對
     * 象不可能被類加載器加載多次,因此序列化破壞不了單例
     * Constructor 的 newInstance()方法中做了強制性的判斷,如果修飾符是Modifier.ENUM 枚舉類型,
     * 直接拋出異常,因此反射破壞不了單例
     *
     * */
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

}

public class ContainerSingleton {
    private ContainerSingleton() {
    }
    //容器式單例,註冊式單例的一種
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

單例模式的優點:
在內存中只有一個實例,減少了內存開銷。
可以避免對資源的多重佔用。
設置全局訪問點,嚴格控制訪問。
單例模式的缺點:
沒有接口,擴展困難。
如果要擴展單例對象,只有修改代碼,沒有其他途徑。

總結

1、私有化構造器
2、保證線程安全
3、延遲加載
4、防止序列化和反序列化破壞單例
5、防禦反射攻擊單例

發佈了21 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章