天天看點

設計模式——單例模式詳解

本篇文章主要介紹設計模式——單例模式詳解。

設計模式——單例模式詳解

單例

介紹

  單例模式(Singleton Pattern)是Java中最基礎最簡單的設計模式之一,這種模式屬于

建立型模式

,提供的就是一種建立對象的方式。這種模式中的單一類建立自己的對象,確定隻有一個對象被建立,并且為所有對象提供一個通路它的全局通路點。

  單例模式用來解決頻繁建立與銷毀執行個體對象問題,當我們想要控制執行個體建立個數或者複用執行個體時,就可以使用單例模式,這樣有助于節省系統資源。

特點

  1. 單例類,顧名思義,隻有一個執行個體。
  2. 單例類必須是自己建立自己的唯一執行個體。
  3. 單例類必須給所有對象提供這個唯一執行個體。
  4. 構造函數私有。

應用場景

适用範圍

  1. 頻繁的通路資料庫或檔案的對象。
  2. 頻繁的需要執行個體化,然後銷毀的對象。
  3. 建立對象耗時長且耗資源,但是又需要常用的對象。

使用舉例

  1. Windows的任務管理器,每次隻能打開一個。
  2. 應用程式的日志應用。
  3. 網上線上人數統計。
  4. 配置檔案的通路類。
  5. 資料庫的連接配接池。
  6. 多線程的線程池。
  7. 作業系統的檔案系統。

設計思路

  1. 一個類每次都傳回唯一的對象(每次傳回都是同一個對象執行個體)。
  2. 提供一個擷取該執行個體的方法。(基本上都是靜态方法)

加載分類

  1. 提前加載

    :在應用開始時就建立單例執行個體。
  2. 延遲加載

    :在

    getInstance()方法

    首次被調用時才調用單例建立,即需要使用時,才加載建立單例對象執行個體。

單例分類

1 餓漢式單例

  1. 類加載時就初始化好執行個體對象,容易産生垃圾對象,浪費記憶體。
  2. 無鎖,執行效率高。
  3. 天生線程安全,因為類加載時就已初始化好執行個體。

代碼

/**
 * 餓漢式單例
 * @author Andya
 * @date 2021/3/9
 */
public class EHanSingleInstance {

    //餓漢式:初始化時就建好執行個體
    private static final EHanSingleInstance instance = new EHanSingleInstance();

    private EHanSingleInstance() {
        doSomething();
    }

    public static EHanSingleInstance getInstance() {
        return instance;
    }

    public void doSomething() {

    }
}
           

2 懶漢式單例

  1. 餓漢式屬于延遲加載初始化。
  2. 若不加鎖,則為非線程安全的。
  3. 若加鎖,則為線程安全的,但效率很低,基本上每次都要同步。

無鎖懶漢式代碼

/**
 * 無鎖懶漢式單例,非線程安全
 * @author Andya
 * @date 2021/3/9
 */
public class LanHanNoLockSingleInstance {

    //懶漢式:初始化時不建立執行個體,等需要時再建立
    private static LanHanNoLockSingleInstance instance;

    private LanHanNoLockSingleInstance() {
        doSomething();
    }

    public static LanHanNoLockSingleInstance getInstance() {
        //先判斷是否為空,若為空建立一個新的執行個體
        if (instance == null) {
            instance = new LanHanNoLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}
           

加鎖懶漢式代碼

/**
 * 加鎖懶漢式單例,線程安全
 * @author Andya
 * @date 2021/3/9
 */
public class LanHanWithLockSingleInstance {

    //懶漢式:初始化時不建立執行個體,等需要時再建立
    private static LanHanWithLockSingleInstance instance;

    private LanHanWithLockSingleInstance() {
        doSomething();
    }

    public static synchronized LanHanWithLockSingleInstance getInstance() {
        //先判斷是否為空,若為空建立一個新的執行個體
        if (instance == null) {
            instance = new LanHanWithLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}
           

3 雙重檢鎖式單例

  在多線程應用中使用這種模式可以保證線程安全。因為如果執行個體為空,有可能存在兩個線程同時調用

getInstance()

方法的情況,這樣的話,第一個線程會首先使用新構造器執行個體化一個單例對象,但此時它還沒有完成單例對象的執行個體化操作,同時第二個線程也檢查到單例執行個體為空,也會開始執行個體化單例對象,這就造成了2次執行個體化對象。是以多線程應用中,需要

來檢查執行個體是否線程安全。

  1. 靜态方法鎖
public static synchronized Singleton getInstance()
           
  1. 代碼塊鎖
synchronized(SingleInstance.class){
	if (instance == null) {
		instance = new SingleInstance();
	}
}
           

  雖然加鎖可以保證線程安全,但是會帶來延遲,因為加鎖後,代碼塊在同一時刻隻能被一個線程執行,但是同步鎖隻有在執行個體沒被建立的時候才會起作用。如果單例執行個體已經被建立,其實不需要走該步驟。是以,我們可以在代碼塊鎖外面再加一層執行個體空判斷。

instance == null

被檢查

2

次。

if (instance == null) {
	synchronized(SingleInstance.class){
		if (instance == null) {
			instance = new SingleInstance();
		}
	}
}
           

/**
 * 雙檢鎖單例
 * @author Andya
 * @date 2021/3/9
 */
public class DoubleCheckSingleInstance {

    //使用volatile保證多線程的可見性
    private static volatile DoubleCheckSingleInstance instance;

    private DoubleCheckSingleInstance() {
        doSomething();
    }

    public static DoubleCheckSingleInstance getInstance() {
        //第一次檢查是否建立過該單例
        if (instance == null) {
            //加鎖,保證線程安全
            synchronized (DoubleCheckSingleInstance.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleInstance();
                }
            }
        }
        return instance;
    }

    public void doSomething() {

    }
}
           

4 靜态内部類單例

  1. 延遲加載初始化執行個體instance。
  2. 線程安全。
  3. 該方式隻适用于靜态域的情況。
  4. 該方式單例不會立即初始化,隻有顯式調用getInstance()方法時,才會顯式轉載靜态内部類,才會執行個體化instance。

/**
 * 靜态内部類單例
 * @author Andya
 * @date 2021/3/9
 */
public class StaticInternalSingleInstance {

    //靜态内部類
    private static class StaticInternalSingleInstanceHolder{

        private static final StaticInternalSingleInstance INSTANCE
                = new StaticInternalSingleInstance();

    }

    private StaticInternalSingleInstance() {
        doSomething();
    }

    public static final StaticInternalSingleInstance getInstance() {
        return StaticInternalSingleInstanceHolder.INSTANCE;
    }

    public void doSomething() {

    }

}
           

5 枚舉類單例

  1. 不屬于延遲加載初始化執行個體instance。
  2. 多線程安全。
  3. 支援序列化機制,進而防止多次執行個體化。

/**
 * 枚舉型單例
 * @author Andya
 * @date 2021/3/9
 */
public enum EnumSingleInstance {

    INSTANCE;

    public EnumSingleInstance getInstance(){
        return INSTANCE;
    }
}
           

總結

  一般在開發應用中,建議使用第1種餓漢式單例,而不推薦使用第2種懶漢式單例,除非明确需要延遲加載時,才會使用第4種靜态内部類單例,若涉及到反序列化建立對象時,推薦使用第5種枚舉式單例。其他也可以考慮使用第3種雙重檢鎖式單例。

燒不死的鳥就是鳳凰

繼續閱讀