Java學習筆記之--------單例模式(一)

單例模式的核心作用

保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點。

單例模式的應用場景

Windows的Task Manager(任務管理器)。

Windows的Recycle Bin(回收站)。在整個系統運行過程中,回收站一直維護着僅有的一個實例。

項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數據,都new一個對象去讀取。

網站的計數器,一般也是採用單例模式實現,否則難以同步。

應用程序的日誌應用,一般都使用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。

數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。

操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。

Application也是單例的典型應用。(Servlet編程中會涉及到)

在Spring中,每個Bean默認就是單例的,這樣做的優點是Spring容器可以管理。

在servlet編程中,每個servlet也是單例。

在spring MVC框架/struts框架中,控制器對象也是單例。

單例模式的優點

由於單例模式只生成一個實例,減少了系統性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時直接產生一個單例對象,然後永久駐留內存的方式來解決。

單例模式可以在系統設置全局的訪問點,優化共享資源訪問,例如可以設計一個單例類,負責所有數據表的映射處理。

常見的單例模式(五種)

1.餓漢式:線程安全,調用頻率高。不能延遲加載。

2.懶漢式:線程安全,調用效率不高。可以延遲加載。

3.雙重檢測鎖式:由於JVM底層內部模型原因,偶爾會出現問題。不建議使用。

4.靜態內部類式:線程安全,調用效率高。可以延遲加載。

5.枚舉單例:線程安全,調用效率高,不能延遲加載。可以天然的防止反射和反序列化漏洞。

注:延遲加載:當在真正需要數據的時候,才真正執行數據加載操作。可以簡單理解爲,只有在使用時纔會發sql語句進行查詢。

單例模式----餓漢式實現:單例對象立即加載

public class SingletonDemo01 {
    
    //類初始化時,立即加載這個對象(沒有延時加載的優勢)!由於加載類時,天然的是線程安全的!
    private static SingletonDemo01 instance = new SingletonDemo01();
    
    private SingletonDemo01(){
    }
    
    //方法沒有同步,調用效率高!
    public static SingletonDemo01 getInstance(){
        return instance;
    }
    
}

餓漢式單例模式代碼中,static變量會在類加載時初始化,此時也不會涉及多個線程對象訪問該對象的問題。虛擬機保證只會裝載一次該類,肯定不會發生併發訪問的問題。因此,可以省略synchronized關鍵字。

存在的問題:如果只是加載本類,而不是要調用getInstance(),甚至永遠沒有調用,則會造成資源浪費。

單例模式----懶漢式實現:單例對象延遲加載

public class SingletonDemo02 {

    //類初始化時,不初始化這個對象(延遲加載,真正用的時候再創建)。
    private static SingletonDemo02 instance;

    //私有化構造器
    private SingletonDemo02(){

    }

    //方法同步,調用效率低!
    public static synchronized SingletonDemo02 getInstance(){
        if (instance == null){
            instance = new SingletonDemo02();
        }
        return instance;
    }

}

懶加載,延遲加載,只有真正用的時候才加載。

存在的問題:雖然資源利用率高了,但是每次調用getInstance()方法都要同步,併發效率較低。

單例模式----雙重檢測鎖實現

public class SingletonDemo03 {

	private static volatile SingletonDemo03 instance = null;

	public static SingletonDemo03 getInstance(){
		if (instance == null){
			SingletonDemo03 sc;
			synchronized (SingletonDemo03.class){
				sc = instance;
				if (sc == null){
					synchronized (SingletonDemo03.class){
						if (sc == null){
							sc = new SingletonDemo03();
						}
					}
					instance = sc;
				}
			}
		}
		return instance;
	}

	private SingletonDemo03(){

	}

}

這個模式將內容同步到下方if內部,提高了執行的效率,不必每次獲取對象都進行同步,只有第一次才同步,創建了以後就沒必要了。

存在的問題:由於編譯器優化原因和JVM底層內部模型原因,偶爾會出問題。不建議使用(實際工作中用不到)。

注意:在1.4以及更早版本的Java中,許多JVM對於volatile關鍵字的實現會導致雙重檢查加鎖的失效。如果使用的是舊版本的jdk,就不要用這個方法來實現單例模式。

單例模式----靜態內部類實現方式

public class SingletonDemo04 {

	public static class SingletonClassInstance {
		private static final SingletonDemo04 instance = new SingletonDemo04();
	}

	public static SingletonDemo04 getInstance() {
		return SingletonClassInstance.instance;
	}

	private SingletonDemo04(){

	}

}

外部類沒有static屬性,則不會像餓漢式那樣立即加載對象。

只有真正調用getInstance(),纔會加載靜態內部類。加載類時是線程安全的。instance是static final類型,保證了內存中只有這樣一個實例存在,而且只能被賦值一次,從而保證了線程安全性。

兼備了併發高效調用和延遲加載的優勢。

靜態內部類實現方式也是一種懶加載方式。使用較多。

單例模式----使用枚舉實現單例模式

public enum  SingletonDemo05 {

	//這個枚舉元素,本身就是單例對象!
	INSTANCE;

	//添加自己需要的操作
	public void singletonOperation(){

	}

	public static Object INSTANCE() {
		return INSTANCE;
	}
}

使用枚舉實現單例模式,實現簡單。枚舉本身就是單例模式。由JVM從根本上提供保障。避免通過反射和反序列化的漏洞。

存在的問題:無延遲加載。

各種單例模式的適用場景

當單例對象佔用資源較少,不需要延遲加載的時候,枚舉實現單例模式優於餓漢式。

當單例對象佔用資源較大,需要延遲加載的時候,靜態內部類的實現方式優於懶漢式。

單例模式的類圖

getInstance()方法是靜態的,意味着它是一個類方法,所以可以在代碼的任何地方使用SingletonDemo01.getInstance()訪問它。和訪問全局變量一樣,只是這裏我們可以延遲實例化。(類圖是由idea自帶的工具生成的)

 

 

以上爲單例模式的學習筆記,此文章爲尚學堂視頻的學習筆記+自己總結。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章