設計模式 -建立型模式之單例模式的五種實現

單例模式(Singleton)

單例模式是在 GOF的23種設計模式裏較爲簡單的一種,下面引用百度百科介紹:面試

單例模式,是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例設計模式

許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。安全

在Java中,確保一個類只有一個對象實例能夠經過權限的修飾來實現。服務器

單例模式 - 餓漢模式

單例模式的餓漢模式指全局的單例實例在第一次被使用時構建。
具體實現:多線程

// 單例模式的餓漢模式實現
  public class Singleton {
    private final static Singleton SINGLETON= new Singleton();
    // Private constructor suppresses   
    private Singleton() {}
 
    // default public constructor
    public static Singleton getInstance() {
        return SINGLETON;
    }
  }

在餓漢模式實現方式中,程序的主要特色是:併發

  1. 私有構造方法
  2. 私有靜態屬性,維護自身實例
  3. 靜態服務方法,獲取實例
  4. 初始化時候建立,消耗初始化系統資源

單例模式 - 懶漢模式 - 普通

懶漢模式,也是最經常使用的形式,餓漢模式讓程序在初始化時候進行加載,有時爲了節約資源,咱們須要在須要的時候進行加載,這時候咱們能夠使用懶漢模式。
具體實現:網站

public class SingletonLayload { 
    // 私有化自身類對象
    private static SingletonLayload SINGLETON;
    // 私有化構造方法
    private SingletonLayload() {}
    
    // 靜態方法獲取實例
    public static SingletonLayload getInstance() {
        if(SINGLETON== null ) {
            SINGLETON= new SingletonLayload();
        }
        return SINGLETON;
    }
}

單例模式 - 懶漢模式 - 同步鎖

在多線程的環境中,簡單的單例模式將會出現問題,試想在上面的懶漢模式中,若是多線程併發執行getInstance(),當線程A執行到:.net

INSTANCE = new SingletonLayload();線程

卻尚未執行完畢時,線程B執行到if(INSTANCE == null ),此時就沒法保證單例特性。
所以在多線程環境中,單例模式須要使用同步鎖確保實現真正的單例。
具體實現:設計

public class SingletonLayloadSyn {
    // 私有化自身類對象
    private static SingletonLayloadSyn SINGLETON;
    // 私有化構造方法
    private SingletonLayloadSyn() {}
    // 靜態方法獲取實例
    public static synchronized SingletonLayloadSyn getInstance() {
        if(SINGLETON == null ) {
            SINGLETON = new SingletonLayloadSyn();
        }
        return SINGLETON;
    }

}

經過在getInstance()方法上添加 synchronized 關鍵字能夠解決多線程帶來的問題。

單例模式 - 懶漢模式 - 雙重校驗鎖

使用上面的( 多線程下 - 懶漢模式 - 同步鎖)方式在解決多線程問題時雖然能夠達到確保線程安全的目的,可是使用了synchronized關鍵字以後在須要屢次調用時,會讓代碼的執行效率大大下降。那麼有沒有在確保線程安全的同時又能夠兼顧效率的方法呢?
具體實現:

public class SingletonLayLoadSynDCL {
    // 私有化自身類對象
    private static SingletonLayLoadSynDCL SINGLETON;
    // 私有化構造方法
    private SingletonLayLoadSynDCL() {
    }

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

使用 synchronized 確保線程安全,在SINGLETON 爲 null 時才進行建立實例,可是仍然不能 保證在實例未建立完成時候有新的線程執行到 if (SINGLETON == null);所以,仍然不夠安全。
修改 getInstance()方法。
具體實現:

public class SingletonLayLoadSynDCL {
    // 私有化自身類對象
    private static SingletonLayLoadSynDCL SINGLETON;
    // 私有化構造方法
    private SingletonLayLoadSynDCL() {
    }
    
    // 使用雙重校驗鎖確保線程安全的同時兼顧執行效率
    public static SingletonLayLoadSynDCL getInstance() {
        if (SINGLETON == null) { // 第一重檢查
            synchronized (SingletonLayLoadSynDCL.class) {
                if (SINGLETON == null) { //第二重檢查
                    SINGLETON = new SingletonLayLoadSynDCL();
                }
            }
        }
        return SINGLETON;

    }
}

看似完美的雙檢查模式,在理論上是沒有問題的。可是在實際的狀況裏,有可能發生在沒有構造完畢的狀況下SINGLETON 引用已經不是 NULL 的狀況,這時候若是有其餘線程執行到if (SINGLETON == null) { // 第一重檢查則會獲取到一個不正確的 SINGLETON 引用。這是因爲JVM 的無序寫入引發的。

幸虧,在 JDK1.5 以後,提供了volatile關鍵字,用於確保被修飾的變量的讀寫不容許被控制。所以修改上面具體實現爲:

/**
 * <p>
 * 使用雙重校驗鎖以及volatile關鍵字確保線程安全的同時兼顧執行效率
 * @author  niujinpeng
 */
public class SingletonLayLoadSynDCL {
    // 私有化自身類對象
    //  private static SingletonLayLoadSynDCL SINGLETON;
    private volatile static SingletonLayLoadSynDCL SINGLETON;
    // 私有化構造方法
    private SingletonLayLoadSynDCL() {}

    // 使用雙重校驗鎖確保線程安全的同時兼顧執行效率
    public static SingletonLayLoadSynDCL getInstance() {
        if (SINGLETON == null) {
            synchronized (SingletonLayLoadSynDCL.class) {
                if (SINGLETON == null) {
                    SINGLETON = new SingletonLayLoadSynDCL();
                }
            }
        }
        return SINGLETON;

    }
}

單例模式 - 懶漢模式 - 內部類

除了使用上面的懶漢模式實現方式以外,在解決多線程問題中,《Effective Java》的做者給出了另一種保證線程安全且兼顧效率的方式,利用了靜態內部類以及類加載特性實現。靜態內部類只有在調用時纔會加載,而靜態屬性隨着類的加載而加載,類的加載初始化只會有一次。所以保證了獲取實例的惟一性。
具體實現:

package cn.snowflow.pattern.singleton;
/**
 * <p>
 * 利用靜態內部類實現線程安全且兼顧效率的單例模式
 * @author  niujinpeng
 */
public class SingletonLayloadSynSafe {
    //靜態內部類
    public static class SingletonHolder{
        static final SingletonLayloadSynSafe INSTANCE = 
            new SingletonLayloadSynSafe();
    }
    // 私有化構造方法
    private SingletonLayloadSynSafe() {}
    
    // 公有方法獲取實例
    public static SingletonLayloadSynSafe getInstance() {
        return SingletonHolder.INSTANCE;
    }

}

若是使用單例模式-餓漢模式,推薦【單例模式 - 餓漢模式】
若是使用單例模式-懶漢模式,推薦【單例模式 - 懶漢模式 - 內部類 】

<完>

我的網站:https://www.codingme.net
若是你喜歡這篇文章,能夠關注公衆號,一塊兒成長。
關注公衆號回覆資源能夠沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。

相關文章
相關標籤/搜索