相信大家去面試的時候,經常被問到單例模式的有關問題吧,今天我們就來好好總結一下
一 懶漢式
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
相信大家對這段代碼再熟悉不過了,懶漢式的意思就是每次在調用getInstance方法時,才去new一個Singleton的實例,但是很明顯,按上面這樣寫有問題啊,線程不同步啊,那麼怎麼辦呢
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在getInstance方法之前加上synchronized不就解決了,但是這樣,每次調用getInstance都會同步,而我們只是希望在第一次new Singleton()的時候同步即可,一旦instance不為空了,就不用再同步了,那麼怎麼做呢
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
在代碼塊利用鎖,並且要加上雙重檢測,因為兩個線程很可能同時執行到第一個判空的地方,可能判斷都為空,就都會進入下面的代碼裡,就會生成兩個對象,所以要加上雙重鎖定,但是這樣就沒問題了嗎,當然不是,我們都知道java內存模型中有無序寫的機制,instance = new Singleton(); 這行其實做了兩個事情:1、調用構造方法,創建了一個實例。2、把這個實例賦值給instance這個實例變量。可問題就是,這兩步jvm是不保證順序的。也就是說。可能在調用構造方法之前,instance已經被設置為非空了
比如順序被重排了,先保證instance 不為null,後面才調用構造函數初始化,這個時候另外一個線程判斷instance 不為空,直接返回instance ,這樣就會有問題,那麼怎麼解決這個問題呢,我們知道 volatile關鍵字可以在instance 賦值的時候,禁止重排序
public class Singleton {
private volatile static Singleton instance; //聲明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
這樣這個問題就解決了,但是是不是覺得很累,下面給出一種完美的解決方法,利用靜態內部類
public class Singleton {
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private Singleton() {
}
//獲取單例對象實例
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
我們知道,靜態內部類只有在getInstance裡第一次被調用的時候才加載,實現了懶加載,而且加載過程是線程安全的,所以一般用這種方法來實現懶漢式單例子
二 餓漢式
//餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
在類加載的時候就new好對象,這樣也不存在線程安全問題,但是餓漢式也有問題,如果構造的單例很大,構造完又遲遲不使用,會導致資源浪費。
總結:推薦使用懶漢式靜態內部類的方式實現單例模式,好了,設計模式-單例模式就總結到這裡,如有問題,歡迎指正,謝謝。