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