設計模式 -你知道單例模式的五種實現嗎?

單例模式(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執行到:

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 核心知識整理&面試資料。

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