關於單例模式你需要知道的都在這了

關於單例模式你需要知道的都在這了

定義

單例模式確保一個類只有一個實例,並提供一個全局訪問點

做法

把類的構造函數設置爲private,用一個靜態變量存儲本身的唯一一個實例,然後通過靜態方法獲取唯一的實例。

應用場景

下面列舉的應用場景引用於https://blog.csdn.net/tanyujing/article/details/14160941

  1. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~
  2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護着僅有的一個實例。
  3. 網站的計數器,一般也是採用單例模式實現,否則難以同步。
  4. 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。
  5. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。
  6. 數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因爲何用單例模式來維護,就可以大大降低這種損耗。
  7. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。
  8. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。
  9. HttpApplication 也是單例模式的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,所有的HttpModule都共享一個HttpApplication實例.

單例模式與全局變量

全局變量也能提供全局訪問點,但全局變量在程序一開始就要初始化,而單例模式可以選擇在加載類的時候創建,也可以選擇在需要實例的時候再創建。最重要的是,全局變量不能保證只有一個實例被創建。

單例模式的不同實現

下面介紹三種常見的單例模式的實現–飢漢式、懶漢式和雙重檢查加鎖式,更多的實現可以參考
https://www.cnblogs.com/zhaoyan001/p/6365064.html

飢漢式單例模式

EagerSingleton.java

package priv.mxz.design_pattern.singleton_pattern;

class EagerSingleton {
    private static EagerSingleton instance=new EagerSingleton();

    private EagerSingleton(){}

    static public EagerSingleton getInstance(){
        return instance;
    }
}


在類加載的時候已經把實例初始化,在調用靜態方法getInstance前已經有了具體的實例,如果程序一直沒有使用這麼實例,則會造成內存上的浪費。由於在類加載的時候已經初始化了實例,所以是線程安全的。

懶漢式單例模式

LazySingleton.java

package priv.mxz.design_pattern.singleton_pattern;

class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton(){ }

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

在調用getInstance後才判斷是否有具體的實例,如果沒有則初始化一個,這樣可以避免在實例還不被使用時佔用內存。
代碼中getInstance用了關鍵字synchronized,後者用於線程同步,保證同一時刻只有一個線程能運行getInstance方法且中途不能切換線程。如果沒有了synchronized,那麼上述代碼就是線程不安全的,因爲可能存在這樣的情況:線程A和線程B都是第一次運行getInstance方法,首先線程A執行完if語句,判斷出需要創建實例,然後準備創建實例,但此時CPU控制權切換到了線程B,此時線程B也執行if判斷,也判斷出需要創建實例,然後線程B創建了自己的實例並返回,切換回線程A,線程A也創建了自己的實例並覆蓋線程B創建的instance並返回,這樣線程AB得到了兩個不同的instance。
加了synchronized後代碼是線程安全的,但每次調用getInstance都需要執行同步操作,實際上只有第一次instance是null時纔可能有同步異常,所以每次都執行同步操作其實降低了性能。

雙重檢查加鎖式(Double Checked Locking)

LazySingletonDCL.java

package priv.mxz.design_pattern.singleton_pattern;

class LazySingletonDCL {
    private volatile static LazySingletonDCL instance;

    private LazySingletonDCL(){}

    public static LazySingletonDCL getInstance(){
        if (instance==null){
            synchronized (LazySingletonDCL.class){
                if (instance==null){
                    instance=new LazySingletonDCL();
                }
            }
        }
        return instance;
    }
}

volatile關鍵字保證了當instance發生變化時(被創建爲實例)多個線程能馬上知道變化發生。避免實例已經被創建,另一個線程依然判斷instance依然爲null的情況。
getInstance方法中,跟懶漢式單例模式對比,DCL式把同步放到了判斷instance爲null後,如果instance不爲null則不會有同步操作,提高了系統性能。
instance爲null的情況下,線程先獲得同步鎖,拿到以後還要再次判斷instance是否爲null,是的話才創建實例,不是的話什麼都不幹。這麼做的原因是兩個線程A和B依然可能先後執行完第一個if判斷,然後同時進入到同步部分,然後線程A獲取到鎖,然後創建了一個實例,隨後鎖交給線程B,此時在同步部分的代碼裏,instance已經不是null(線程B通過volatile知道線程A已經創建了一個實例),如果不再次判斷,那麼線程B還會創建一個實例。這就是需要雙重檢查的原因。

測試代碼

SingletonPattern.java

package priv.mxz.design_pattern.singleton_pattern;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class SingletonPattern {

    public static void beforeGetInstance(Class cls)throws IllegalAccessException{
        Field[] fields=cls.getDeclaredFields();
        if (fields!=null && fields.length>0){
            for (Field field:fields){
                field.setAccessible(true);
                if (field.getType()==cls && Modifier.isStatic(field.getModifiers())){
                    System.out.println("private instance of "+cls.getName()+ " before getInstance() is "+
                            field.get(cls));
                }
            }
        }
    }

    public static void main(String[] args){

        try {
            beforeGetInstance(EagerSingleton.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        try {
            beforeGetInstance(LazySingleton.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        try {
            beforeGetInstance(LazySingletonDCL.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


        EagerSingleton es1=EagerSingleton.getInstance();
        EagerSingleton es2=EagerSingleton.getInstance();
        System.out.println("two EagerSingleton equal: "+ (es1==es2));

//        EagerSingleton es3=new EagerSingleton();  失敗,無法編譯通過

        LazySingleton ls1=LazySingleton.getInstance();
        LazySingleton ls2=LazySingleton.getInstance();
        System.out.println("two LazySingleton equal:"+(ls1==ls2));

        LazySingletonDCL lsd1=LazySingletonDCL.getInstance();
        LazySingletonDCL lsd2=LazySingletonDCL.getInstance();
        System.out.println("two LazySingleton DCL equal:"+(lsd1==lsd2));

    }
}

下面是結果

private instance of priv.mxz.design_pattern.singleton_pattern.EagerSingleton before getInstance() is priv.mxz.design_pattern.singleton_pattern.EagerSingleton@14ae5a5
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingleton before getInstance() is null
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingletonDCL before getInstance() is null
two EagerSingleton equal: true
two LazySingleton equal:true
two LazySingleton DCL equal:true

main函數中我們分別把三種單例模式的類的Class實例傳進函數beforeGetInstance,beforeGetInstance通過反射的方式判斷類中靜態變量instance的值是否爲null,以此來判斷單例模式是飢漢式還是懶漢式(即判斷在getInstance被調用之前instance是否已經初始化),從輸出可以看到EagerSingleton在調用getInstance前已經初始化了實例,其他兩個則沒有,值爲null

接着我們分別用三個類的getInstance各自創建了兩個實例,判斷着兩個實例是否相同,從結果可知,三個類都實現了單例模式的基本功能–返回的兩個實例是完全相同的唯一一個實例。

優缺點

優點:
單例模式可以替代全局變量爲程序提供唯一的全局訪問點,避免了對象的多次的創建與銷燬,提高了系統性能。

缺點:
程序中可能有許多地方依賴於單例類,單例類的職責過於繁重,修改單例類可能牽一髮而動全身。
單例類有創建產品的工廠角色,也有產品本身的角色,違背了“單一職責”的設計原則。
單例類不方便繼承,不利於擴展。

總結

單例模式爲外部提供了單例類唯一的類實例的訪問入口,可以保證在程序運行期間只有一個單例類實例,常見的應用場景有日誌記錄器和垃圾回收器等,單例模式的實現有多種方式,比如飢漢式,懶漢式和DLC式。單例模式的實現有利於提高系統性能,保證了實例的唯一性,但修改單例類時可能會對系統造成較大的影響。

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