單例模式:用來創建獨一無二的,只能有一個實例的對象的入場券。包括的角色如下:
單例角色(Singleton):定義一個GetInstance方法,允許客戶訪問它的唯一實例。GetInstance是一個靜態方法,主要負責創建自己的唯一實例。
UML圖如下:
單例模式有多種寫法各有利弊,現在我們來看看各種模式寫法。
餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
這種方式在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快。這種方式基於類加載機制避免了多線程的同步問題,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到懶加載的效果。
懶漢模式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種寫法能夠在多線程中很好的工作,但是每次調用getInstance方法時都需要進行同步,造成不必要的同步開銷,而且大部分時候我們是用不到同步的,所以不建議用這種模式。
雙重檢查模式 (DCL)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
這種寫法在getSingleton方法中對singleton進行了兩次判空,第一次是爲了不必要的同步,第二次是在singleton等於null的情況下才創建實例。
在這裏使用volatile會或多或少的影響性能,但考慮到程序的正確性,犧牲這點性能還是值得的。DCL優點是資源利用率高,第一次執行getInstance時單例對象才被實例化,效率高。缺點是第一次加載時反應稍慢一些,在高併發環境下也有一定的缺陷,雖然發生的概率很小。DCL雖然在一定程度解決了資源的消耗和多餘的同步,線程安全等問題,但是他還是在某些情況會出現失效的問題,也就是DCL失效,在《Java併發編程實踐》一書建議用靜態內部類單例模式來替代DCL。靜態內部類單例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加載Singleton類時並不會初始化sInstance,只有第一次調用getInstance方法時虛擬機加載SingletonHolder並初始化sInstance,這樣不僅能確保線程安全也能保證Singleton類的唯一性,所以推薦使用靜態內部類單例模式。