設計模式(一)單例模式

1、基本概念

一、單例基本概念

定義:java

  • 確保某個類有且只有一個實例對象

優勢:數據庫

  • 內存中只有一個實例,可減小內存使用,尤爲是須要頻繁建立、銷燬對象的場景,單例模式比較有優點。
  • 避免對資源的多重佔用,好比讀配置、寫文件等操做,只有一個對象去操做資源,避免了多個內存對象對資源的同時操做。
  • 單例可設置全局的訪問點,共享資源訪問。

缺點:設計模式

  • 擴展比較困難,通常單例沒有接口
  • 單例模式跟單一職責原則衝突,單例會將多種業務邏輯放在一個類中。
  • 單例對象若是持有Context,容易引發內存泄露,最好傳入ApplicationContext

使用場景:安全

  • 項目中須要一個共享訪問點或共享數據,例如全局配置文件、數據庫管理類、網絡請求管理類等。
  • 建立對象須要消耗資源比較多,例如訪問IO、數據庫等資源時。

關鍵點:bash

  • 構造函數不對外開發
  • 確保多線程環境下對象只有一個

二、非線程安全的單例:

public class Singleton {
	private static Singleton singleton = null;
	
	private Singleton(){

	}

	public static Singleton getInstance(){
		if(singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}
}
複製代碼

若是兩個線程同時執行到singleton == null 的判斷的時候,兩個線程條件都知足,會出現建立兩個對象的狀況,違反了單例只建立一個對象的原則。網絡

2、線程安全的單例

一、餓漢式

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }
}
複製代碼
  • 實現原理:經過static修飾Singleton,靜態變量隨着類加載時完成初始化,在內存中只會有一個,全部沒有線程安全的問題。
  • 缺點:先將對象建立出來,並無實際的調用,會形成沒必要要的內存消耗。

二、懶漢式(同步方法)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){

    }
    public synchronized static Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
複製代碼
  • 實現原理:getInstance經過synchronized關鍵字進行修飾,保證只有一個線程執行getInstance方法內的建立邏輯,保證建立出來的對象只有一個。
  • 優勢:用的時候才進行建立,減小沒必要要的內存消耗。
  • 缺點:synchronized關鍵字存在效率問題,線程A執行到getInstance方法時,線程B只能處於等待狀態,只有等線程A執行完getInstance方法,線程B才能繼續執行。

三、懶漢式(雙重檢查加鎖)

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
複製代碼
(1)雙層判斷的做用
  • 外層singleton == null 判斷爲true,才執行synchronized所修飾的內部邏輯,不然直接獲取singleton對象,保證須要有對象建立就不須要等待。
  • 內層singleton == null 判斷是爲了再次檢查對象是否建立。
    爲何:線程A執行到synchronized修飾的代碼時,此時線程B正在執行內部邏輯,因此線程A只能處於等待狀態。線程B執行完畢並將對象建立出來了,而線程A是不知道的,會再次建立,因此須要再次判斷對象是否爲空。
(2)volatile的做用

singleton = new Singleton() 不是原子操做,這段代碼會編譯成多條指令
(1)將對象new出來,給Singleton分配內存空間
(2)調用Singleton構造函數,初始化成員變量
(3)將singleton指向分配的內存,此時singleton就不爲空了
java編譯器容許指令亂序執行,因此二、3步執行順序沒法保證,就可能線程B執行了這段代碼,singleton指向了內存空間,可是成員變量還沒初始化完。此時若是線程A經過singleton== null進行判斷,發現對象不爲空,拿對象去使用,可是成員變量還沒初始化完,就會出錯。多線程

被volatile關鍵字修飾,可以保證內存可見性和禁止進行指令重排序,經過該特性保證了singleton = new Singleton()的指令執行順序函數

  • 實現原理:經過synchronized和volatile實現線程安全。
  • 優勢:縮小了synchronized關鍵字所修飾的範圍
  • 缺點:volatile屏蔽掉了編譯器必要的代碼優化,因此在效率上會有影響

四、靜態內部類

public class Singleton {
    private Singleton() {

    }

    private static class SingletonInner {
        private static final Singleton sInstance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInner.sInstance;
    }
}
複製代碼
  • 實現原理:
    (1)如何保證線程安全:利用了Classloader的機制保證初始化時只有一個線程。虛擬機會保證靜態類初始化的方法在多線程環境被正確加鎖、同步,若是多個線程去初始化,只會有一個線程(線程A)去執行初始化操做,其餘線程(線程B)須要阻塞等待。線程A執行完畢後,線程B喚醒後不會進入初始化的方法。同一個類加載器下,只會初始化一次
    (2)是否爲延遲加載:內部類在被調用時纔會被加載, 雖然Singleton被加載了,可是內部類不須要當即加載,因此Singleton尚未實例化,也算是懶漢式的一種(延遲加載),只有主動調用getInstance方法時Singleton纔會被實例化。
  • 優勢:延遲加載,而且沒有線程同步問題,效率更高。

五、枚舉

public enum Singleton{
    SINGLETON;
}
複製代碼
  • 實現原理:對枚舉進行反編譯後,SINGLETON被聲明爲static的,虛擬機保證一個靜態變量的線程安全問題,因此枚舉是線程安全的。
  • 優勢:《Effective Java》中提到,功能完整、使用簡潔、無償地提供了序列化機制、在面對複雜的序列化或者反射攻擊時仍然能夠絕對防止屢次實例化等優勢
    而其餘單例方式若是禁止在反序列化時從新建立對象,須要在readResolve方法中,將sInstance返回。
private Object readResolve() throws ObjectStreamException {
    return sInstance;
}
複製代碼

參考資料:優化

  • 《設計模式之禪》
  • 《Android源碼設計模式解析與實戰》
相關文章
相關標籤/搜索