一、什么是單例模式?
單例模式(Singleton Pattern),顧名思義,就是被單例的對象只能有一個實例存在。單例模式的實現方式是,一個類能返回對象的一個引用(永遠是同一個)和一個獲得該唯一實例的方法(必須是靜態(tài)方法)。通過單例模式,我們可以保證系統(tǒng)中只有一個實例,從而在某些特定的場合下達到節(jié)約或者控制系統(tǒng)資源的目的。
二、代碼如何實現?
- 餓漢模式
最常見、最簡單的單例模式寫法之一,即在類的一開始就給它新建一個實例。示例如下:
//餓漢模式
class Singleton {
//一開始就創(chuàng)建一個實例
private static Singleton instance = new Singleton();
private Singleton() { }
//獲取實例
public static Singleton getInstance() {
return instance;
}
}
存在的問題:這種方式有一個明顯的缺點,那就是不管有沒有調用過獲得實例的方法,每次都會新建一個實例。
- 懶漢模式
餓漢模式的升級版本,即在類的一開始只創(chuàng)建一個引用,但并不實例化,只有在需要使用到它的時候,先去判斷實例是否為空,如果為空的時候才會新建一個實例來使用。示例如下:
//懶漢模式
class Singleton {
//一開始并不創(chuàng)建實例
private static Singleton instance;
private Singleton() { }
//需要時再新建
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在的問題:該種模式存在一個嚴重的問題。那就是如果有多個線程并行調用getInstance()的時候,還是會創(chuàng)建多個實例的,單例模式就失效了。
- 線程安全的懶漢模式
在懶漢模式的基礎上,把它設為線程同步(synchronized)就好了。synchronize的作用就是保證在同一時刻最多只有一個線程運行,這樣就避免了多線程帶來的問題。示例代碼如下:
//懶漢模式(線程安全)
class Singleton {
private static Singleton instance;
private Singleton() { }
//添加了 synchronized 關鍵字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在的問題:這種模式看似很好的解決了多線程的問題,但是它的效率并不高,每次調用獲得實例的方法時都要進行同步,但是多數情況下并不需要同步操作。
- 雙重檢驗鎖
為了解決第三種模式的問題,延伸出來雙重檢驗鎖模式,即在獲取實例的方法中,先去判斷該實例是否為空,如果為空在再加同步鎖,然后新建實例。示例如下:
//雙重檢驗鎖
class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
//第一個檢驗鎖,如果不為空直接返回實例對象,為空才進入下一步
if (instance == null) {
//第二個檢驗鎖,因為可能有多個線程進入到if語句內,所以加線程同步鎖
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
存在的問題:該種模式此時并不是完美的。主要問題在在于instance = new Singleton();這句代碼,因為JVM(Java虛擬機)執(zhí)行這句代碼的時候,要做好幾件事情,而JVM為了優(yōu)化代碼,有可能造成做這幾件事情的執(zhí)行順序是不固定的,從而造成錯誤。
解決辦法:為了解決上述問題,我們需要給實例加一個volatile關鍵字,它的作用就是防止編譯器自行優(yōu)化代碼。此時,雙重檢驗鎖模式才完美實現。
private volatile static Singleton instance;
- 靜態(tài)內部類
這種方式,利用了 JVM 自身的機制來保證線程安全,因為SingletonHolder類是私有的,除了getInstance()之外沒有其它方式可以訪問實例對象,而且只有在調用getInstance()時才會去真正創(chuàng)建實例對象。示例如下:
//靜態(tài)內部類
class Singleton {
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
private Singleton() { }
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
- 枚舉
我們可以通過 Wife.INSTANCE 來訪問實例對象,而且創(chuàng)建枚舉默認就是線程安全的,還可以防止反序列化帶來的問題。最為推薦。示例如下:
//利用枚舉的方式創(chuàng)建單例
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}