常用設計模式之單例模式,以及單例模式的線程安全問題

常用設計模式之單例模式,以及單例模式的線程安全問題

介紹

有時候允許自由創建某個類的對象沒有意義,還可能會造成系統性能下降。如果讓一個類只能創建一個實例,這種設計模式被稱爲單例設計模式。其基本思路就是:定義一個靜態變量來緩存該類的實例;私有化構造器;然後對外暴露一個靜態方法來獲取該類的實例。單例設計模式根據初始化對象的時機不同又分爲餓漢式和懶漢式。

餓漢式

餓漢式非常簡單,直接貼代碼:

public class Singleton {
	
	private static Singleton instance = new Singleton();
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		return instance;
	}

}

懶漢式

懶漢式是在調用getInstance方法時進行判斷,如果對象已經實例化,則直接返回,如果沒有實例化,先實例化一個對象,再返回。

public class Singleton {

	private static Singleton instance;
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}

}

對多線程比較敏感的程序員可能已經意識到了,這樣的方式是存在線程安全問題的,並且初始化對象所需的時間越久,越有可能出現線程安全問題,你如果不信,我們可以做一個實驗,在構造器裏sleep兩毫秒來模擬延遲,然後對getInstance方法做併發測試。

	private Singleton() {
		try {
			Thread.sleep(2);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread th = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		Thread th1 = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		Thread th2 = new Thread(() -> {
			Singleton instance = Singleton.getInstance();
			System.out.println(instance);
		});
		th.start();
		th1.start();
		th2.start();
	}

輸出的結果爲:
thread.Singleton@6c7d6e81
thread.Singleton@5c6ef4da
thread.Singleton@e50a5a2

不難發現這幾個線程獲取到的對象的內存地址並不相同,說明他們不是同一個對象。所以getInstance方法要加鎖。關於多線程相關知識,有興趣的小夥伴們可以看下我的另外一篇博客——Java多線程基礎知識總結

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

再執行一下剛纔的併發測試,發現打印的地址都是相同的。這樣每個線程在進入getInstance方法後,都會先獲取鎖,沒拿到鎖的線程會阻塞,直到拿到鎖才能執行。很顯然,在高併發的情況下讓每個線程都去競爭鎖不太高效,比較高效的做法是:先判斷一下,如果已經實例化了就直接返回,就不用去競爭鎖了,代碼如下:

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

這種寫法叫DCL,即Double Checked Locking.
還有一種更極端的情況,就是“指令重排”問題。這裏不打算介紹“指令重排”,看客大佬們可以自己去搜下。所以爲了代碼的極致嚴謹,給我們的instance屬性加上volatile修飾,至此,懶漢模式的完整代碼如下:

public class Singleton {
	
	private static volatile Singleton instance;
	
	private Singleton() {
	}
	
	public static Singleton getInstance() {
		if (instance != null) {
			return instance;
		}
		synchronized(Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
		
		return instance;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章