DesignPattern - 單例模式【建立型】

歡迎關注微信公衆號:FSA全棧行動 👋java

1、單例模式介紹

經過單例模式能夠保證系統中,應用該模式的類只有一個對象實例。安全

  • 好處:微信

    • 節省內存(不必存在 多個功能相同的對象)
    • 提升性能(有些對象的建立很耗系統資源)
  • 分類:markdown

    • 餓漢:提早建立對象(class 一加載就建立)
    • 懶漢:延遲建立對象(調用 getInstance() 時再建立)
  • 實現步驟:多線程

    • 私有化構造函數
    • 提供獲取單例的方法

2、單例模式代碼實現

一、餓漢式

  • 餓漢方式:提早建立好對象
  • 優勢:實現簡單,沒有多線程同步問題
  • 缺點:類一加載就會建立,無論有沒有使用,instance 對象一直佔着內存
/** * 單式模式:餓漢式 * * @author GitLqr */
public class SingletonHungry {

	private static SingletonHungry instance = new SingletonHungry();

	private SingletonHungry() {
	}

	public static SingletonHungry getInstanceHungry() {
		return instance;
	}
}
複製代碼

二、懶漢式

  • 懶漢方式:延遲建立對象
  • 優勢:須要用到時纔會建立對象,規避沒必要要的內存浪費
  • 缺點:可能須要考慮有多線程同步問題

1)DCL (+ volatile) 方式

DCL,即雙重檢查鎖定 (Double-Checked-Locking)函數

  • synchronized 前第一次判空:避免沒必要要的加鎖同步,提升性能
  • synchronized 後第二次判空:避免出現多線程安全問題
  • volatile 修飾 instance:避免指令重排序問題
/** * 單例模式:懶漢式 (DCL + volatile) * * @author GitLqr */
public class SingletonLazy {

	private static volatile SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
複製代碼

2)靜態內部類 方式

  • Holder 靜態內部類:外部類加載時,並不會直接致使靜態內部類被加載,在調用 getInstance() 時纔會觸發該靜態內部類被加載,因此,能夠延時執行。
  • Holder.instance static 字段:同一個加載器下,一個類型只會初始化一次,故自然的線程安全。
/** * 單例模式:懶漢式 (靜態內部類) * * @author GitLqr */
public class SingletonLazyClass {

	private static class Holder {
		private static SingletonLazyClass instance = new SingletonLazyClass();
	}

	public static SingletonLazyClass getInstance() {
		return Holder.instance;
	}

	private SingletonLazyClass() {
	}
}
複製代碼

注意:靜態內部類 相比 DCL 代碼簡潔不少,即有餓漢式的優點,又能夠作到延時初始化,看似很完美,但其有一個致命問題,即沒法傳參,因此,實際開發中,要根據實際狀況來選擇其中一種實現方式。oop

3)枚舉 方式

  • 枚舉沒法 new,也就無須私有化構造函數,相比 靜態內部類 方式要簡潔的多
  • 枚舉實例建立是線程安全的
/** * 單例模式:懶漢式 (枚舉) * * @author GitLqr */
public enum SingletonEnum {
	INSTANCE;

    // 枚舉與普通類同樣,能夠擁有字段和方法
	public void method() {
		// TODO
	}
}
複製代碼

注意:缺點跟 靜態內部類 方式同樣,外部沒法傳參。性能

3、DCL 單例模式的演進

階段一:簡單的對象判空

  • 缺點:線程不安全,多線程下存在安全問題
  • 解決辦法:加鎖
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			instance = new SingletonLazy();
		}
		return instance;
	}
}
複製代碼

階段二:經過加鎖 synchronized 保證單例

  • 缺點:採用 synchronized 對方法加鎖有很大的性能開銷
  • 解決辦法:鎖粒度不要這麼大
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static synchronized SingletonLazy getInstance() {
		if (instance == null) {
			instance = new SingletonLazy();
		}
		return instance;
	}
}
複製代碼

階段三:DCL 雙重檢查鎖定 (Double-Checked-Locking)

  • 優勢:在多線程狀況下保持高性能
  • 缺點:不安全,instance = new SingletonLazy(); 不是原子性操做,這行代碼爲以下 3 步:
    1. 分配內存空間
    2. 在空間內建立對象
    3. 對對象賦值給引用
  • 緣由:由於 JVM 指令重排序問題,可能致使線程中會按 1->3->2 的順序執行(JVM 認爲 2 和 3 沒有前後順序),其中步驟 3 會把 線程副內存中 的值寫入主內存,其餘線程就會讀取到 instance 最新的值,但由於步驟 2 尚未執行,此時這個 instance 是不徹底的對象,這時其餘線程中使用這個不徹底的 instance 就會出錯。
  • 解決辦法:使用 volatile 關鍵字
public class SingletonLazy {

	private static SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
複製代碼

階段四:DCL + volatile

  • volatile:是 java 提供的關鍵字,能夠禁止指令重排序
public class SingletonLazy {

	private static volatile SingletonLazy instance;

	private SingletonLazy() {
	}

	public static SingletonLazy getInstance() {
		if (instance == null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
複製代碼

若是文章對您有所幫助, 請不吝點擊關注一下個人微信公衆號:FSA全棧行動, 這將是對我最大的激勵. 公衆號不只有Android技術, 還有iOS, Python等文章, 可能有你想要了解的技能知識點哦~spa

相關文章
相關標籤/搜索