設計模式:單例模式多種實現及應用場景Java版

開篇

設計模式對於很多小夥伴來說都是它認識你,但是你不認識它,設計模式可以幫助我們簡化代碼,提高代碼的複用率,減少代碼的耦合性,以及增加代碼的重複利用性,但是設計模式並非是好用的代言,有些時候也會給我們代來很多問題,比如簡單的判斷語句會變成複雜的多類關聯,也會引發一些安全問題,比如今天要說的單例模式。

基本介紹

單例模式(Singleton Pattern)是Java中最簡單的設計模式之一(暗藏玄機)。屬於創建模式之一,提供了一種創建對象的最佳方式。一個單一的類,負責創建自己的對象,同時保證只有單個對象被創建,對外提供唯一的方法來獲取該類的對象,並且不需要實例化對象。因此可以總結出以下幾點:

  1. 只能有一個實例(注意防止併發)。
  2. 實例必須是自己創建的。
  3. 必須給其他對象這一實例。

主要解決

一個被全局使用的類,被頻繁的創建,當你想控制資源,節省資源時,你就要使用單例模式。

關鍵代碼

  1. 默認構造函數要私有。
  2. 提供對外獲取對象的唯一方法。

使用場景

  1. Spring中Bean容器管理的對象,默認下就是通過單例構建對象
  2. 系統中ID生成器,通常採用單例模式創建出對象
  3. 比如定時任務中,發現任務和觸發執行器執行的類,一般都設計成單例模式獲取(頻繁使用)

缺點

  1. 沒有接口,不能被繼承 
  2. 控制不好會有併發問題,除了枚舉類之外,可以被反射獲取新的實例
  3. 單一原則下,類應該只關心內部,不應該關心外部的創建,違背單一原則

具體實現並帶有優缺點分析

1、餓漢式

/**
 * 餓漢式
 * 利用classload上來初始化實例,解決併發問題
 * 但是此方法比較大的問題就是,當初始化單利比較耗時,或者很久不會使用的時候,浪費內存
 * 是最長用方案
 */
public class SingleObject {
    //單例模式必然是上來就私有化
    private SingleObject() {
    }
    //創建一個唯一的類對象
    private static SingleObject instance = new SingleObject();
    //提供一個獲取的方法
    public static SingleObject getInstance() {
        return instance;
    }
}

2、懶漢式,不考慮併發

/**
 * 因此引出懶漢式,節省內存
 * 但是此情況又會出現線程併發問題
 * 在判斷 instance == null 的時候可能出現併發,多線程同時到達new
 */
class SingleObjectLazy {
    //依然是上來私有構造
    private SingleObjectLazy() {
    }

    //創建靜態對象變量,不初始化
    private static SingleObjectLazy instance;

    //使用時在初始化
    public static SingleObjectLazy getInstance() {
        if (instance == null) {
            instance = new SingleObjectLazy();
        }
        return instance;
    }
}

3、懶漢式,加鎖

/**
 * 由於併發引出加鎖,
 * 但是很難出現同時獲取instance而且還是爲空的情況
 * 一旦創建後,不在需要鎖住
 * 此時就會造成效率低下
 */
class SingleObjectSyncLazy {

    private SingleObjectSyncLazy() {
    }

    private static SingleObjectSyncLazy instance;

    public static synchronized SingleObjectSyncLazy getInstance() {
        if (instance == null) instance = new SingleObjectSyncLazy();
        return instance;
    }
}

4、懶漢式,雙重檢查代替方法鎖,提高效率,防止指令重排加入volatile

/**
 * 使用雙重檢查機制
 * 只有當併發null的時候才需要進入加鎖,此時併發的概率很小
 * 還是有問題的,不加上 volatile修飾的話,會出指令重排
 * 同時 synchronized  只保單線程的結果正確,不會保證指令重排,
 */
class SingleObjectSyncLazyDouble {
    private SingleObjectSyncLazyDouble() {
    }

    //防止指令重排
    private volatile static SingleObjectSyncLazyDouble instance;

    public static SingleObjectSyncLazyDouble getInstance() {
        if (instance == null) { //只有併發null的時候進入加鎖
            synchronized (SingleObjectSyncLazyDouble.class) {
                //as-if-serial synchronized 只會保證單線程內執行操作結果,指令依然會重排
                if (instance == null) { //不加,就會直接創建兩個
                    /*此處會出現指令重排
                     *  instance = new SingleObjectSyncLazyDouble();
                     *  1、開闢內存
                     *  2、實例化
                     *  3、引用指向
                     *  不加volatile 就會出現混亂效果,導致先引用,這時候第二線程剛好到達第一處檢查,
                     *  直接走人了。就出現了大問題
                     * */
                    instance = new SingleObjectSyncLazyDouble();
                }
            }
        }
        return instance;
    }
}

5、懶加載、靜態內部類實現。

/**
 * 靜態內部類的使用,靜態內部類內部的方法是在第一次調用的時候纔會加載,因此可以避免一上來就被初始化的問題
 * 也是一種懶加載的形式
 */
class SingleObjectStaticLazy {
    private SingleObjectStaticLazy() {
    }

    private static class StaticClass {
        private static SingleObjectStaticLazy instance = new SingleObjectStaticLazy();
    }

    public static SingleObjectStaticLazy getInstance() {
        return StaticClass.instance;
    }
}

6、枚舉

以上都可以通過反射獲取新的實例,枚舉類被反射會直接觸發異常

該方法在1.5纔開始引入,不受衆,不過是小黃書推薦的方式。

/**
 * 枚舉
 */
public enum SingleObject {
    INSTANCE;
}

結束

以上就是單例模式創建的方式,優點缺點,希望可以對大家有所幫助 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章