單例模式

定義

    單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。

理解

    在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了避免不一致狀態,避免政出多頭。

單例模式的實現

懶漢式

線程不安全

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

該寫法採用lazy loading,但是線程不安全。若有兩條線程同時訪問getInstance(),可能會創建兩個實例

線程安全

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static synchronized Singleton getInstance() {
	if (instance == null) {
	    instance = new Singleton();
	}
	return instance;
    }
}
這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。所以,我們對代碼塊加鎖,而不是整個方法
public class Singleton {
    private static volatile Singleton singleton = null;
     
    private Singleton(){}
     
    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }   
}
    這種寫法兼顧了效率和線程安全,被稱爲“雙重檢查鎖”,進行兩次null檢查。看似多此一舉,但實際上卻極大提升了併發度,進而提升了性能。爲什麼可以提高併發度呢?,實際開發中,在單例中new的情況非常少,絕大多數都是可以並行的讀操作。因此在加鎖前多進行一次null檢查就可以減少絕大多數的加鎖操作,執行效率提高的目的也就達到了。

餓漢式

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

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

這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。

靜態內部類

那麼,有沒有一種延時加載,並且能保證線程安全的簡單寫法呢?我們可以把Singleton實例放到一個靜態內部類中,這樣就避免了靜態實例在Singleton類加載的時候就創建對象,並且由於靜態內部類只會被加載一次,且其加載過程是線程安全的。當第一條線程訪問getInstance()方法,加載靜態內部類,此時,第二條線程訪問getInstance()方法,由於此時正在加載靜態內部類,該過程線程安全,不會重複new Singleten()。所以這種寫法也是線程安全的:
public class Singleton {
    private static class SingletonHolder {
	private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
	return SingletonHolder.INSTANCE;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章