初學 Java 設計模式(六):實戰單例模式

1、單例模式介紹

1. 解決的問題

  • 保證一個類只有一個實例。 最多見的是控制某些共享資源 (例如數據庫或文件) 的訪問權限。

    運做方式是這樣的: 若是建立了一個對象,同時過一下子後決定再建立一個新對象,此時會得到以前已建立的對象, 而不是一個新對象。 java

    注意, 普通構造函數沒法實現上述行爲,由於構造函數的設計決定了它必須老是返回一個新對象。git

  • 爲該實例提供一個全局訪問節點。 還記得用過的那些存儲重要對象的全局變量嗎?它們在使用上十分方便,但同時也很是不安全,由於任何代碼都有可能覆蓋掉那些變量的內容,從而引起程序崩潰。

    和全局變量同樣, 單例模式也容許在程序的任何地方訪問特定對象。同時能夠保護該實例不被其餘代碼覆蓋。程序員

2. 定義

單例模式是一種建立型設計模式,可以保證一個類只有一個實例,並提供一個訪問該實例的全局節點。github

3. 應用場景

  • 程序中的某個類對於全部客戶端只有一個可用的實例,可使用單例模式。
  • 要更加嚴格地控制全局變量,可使用單例模式。

注意:能夠隨時調整限制並設定生成單例實例的數量,只需修改 獲取實例 方法,即 getInstance 中的代碼便可實現。算法

4. 與其餘設計模式的關係

  • 抽象工廠模式生成器模式原型模式均可以用單例來實現。
  • 外觀模式類一般能夠轉換爲單例模式類,由於在大部分狀況下一個外觀模式就足夠了。
  • 若是能將對象的全部共享狀態簡化爲一個享元對象,那麼享元模式就和單例模式相似。但這個兩個設計模式有兩個根本性的不一樣。數據庫

    1. 只會有一個單例實體,但享元類能夠有多個實體,各實體的內在狀態也能夠不一樣。
    2. 單例對象能夠是可變的。享元對象是不可變的。

2、單例模式優缺點

1. 優勢

  • 能夠保證一個類只有一個實例。
  • 得到了一個指向該實例的全局訪問節點。
  • 僅在首次請求單例對象時對其進行初始化。

2. 缺點

  • 違反了單一職責原則。
  • 單例模式可能掩蓋不良設計,好比程序各組件之間相互瞭解過多等。
  • 該模式在多線程環境下須要進行特殊處理,避免多個線程屢次出建立單例對象。
  • 單例的客戶端代碼單元測試可能會比較困難,由於許多測試框架以基於繼承的方式建立模擬對象。因爲單例類的構造函數是私有的,並且絕大部分語言沒法重寫靜態方法,因此須要仔細考慮模擬單例的方法。

3、7 種單例模式實現

靜態類使用設計模式

/**
 * 靜態類單例實現
 */
public class StaticSingleton {
    public static Map<String,String> hashMap = new ConcurrentHashMap<String, String>();
}
  • 這種方式在咱們的業務開發場景中很是常見,這樣的方式能夠在第一次運行的時候直接初始化 Map 類。
  • 在不須要維持任何狀態,不須要延遲加載,僅僅用於全局訪問時,這種方式更便於使用。
  • 但在須要被繼承、須要維持一些特定狀態時,單例模式更適合。

單例模式的實現方式比較多,主要是實現上是否支持懶漢模式、是否線程安全。安全

接下來經過不一樣的單例模式實現方式來解析單例模式。多線程

1. 懶漢單例模式(線程不安全)

/**
 * 懶漢單例模式(線程不安全)
 */
public class SlobThreadUnsafeSingleton {
    private static SlobThreadUnsafeSingleton instance;

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private SlobThreadUnsafeSingleton() {
    }

    public static SlobThreadUnsafeSingleton getInstance() {
        if (ObjectUtils.isEmpty(instance)) {
            instance = new SlobThreadUnsafeSingleton();
        }
        return instance;
    }
}

這種懶漢單例模式知足了懶加載,但當多個訪問者同時獲取對象實例時(多進程),會致使多個實例並存,沒有達到單例模式的需求。架構

2. 懶漢單例模式(線程安全)

/**
 * 懶漢單例模式(線程安全)
 */
public class SlobThreadSafeSingleton {
    private static SlobThreadSafeSingleton instance;

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private SlobThreadSafeSingleton() {
    }

    public static synchronized SlobThreadSafeSingleton getInstance() {
        if (ObjectUtils.isEmpty(instance)) {
            instance = new SlobThreadSafeSingleton();
        }
        return instance;
    }
}

這種懶漢單例模式是線程安全的,但因爲鎖在方法上,全部的訪問都須要鎖佔用,會致使資源的浪費。非特殊狀況,不建議使用此種方式來實現單例模式。

3. 餓漢單例模式(線程安全)

/**
 * 餓漢單例模式(線程安全)
 */
public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private EagerSingleton() {
    }

    private static EagerSingleton getInstance() {
        return instance;
    }
}

餓漢單例模式並非懶加載,簡單來講就是不管是否用到該類都會在程序啓動之初建立。這種方式會致使的問題相似一打開某個軟件,手機直接卡死(開啓了過多無用功能,致使內存不足)。

4. 雙重校驗鎖單例模式(線程安全)

/**
 * 雙重校驗鎖單例模式(線程安全)
 */
public class DoubleCheckingLockingSingleton {
    private static volatile DoubleCheckingLockingSingleton instance;

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private DoubleCheckingLockingSingleton() {
    }

    public static DoubleCheckingLockingSingleton getInstance() {
        if (ObjectUtils.isNotEmpty(instance)) {
            return instance;
        }
        synchronized (DoubleCheckingLockingSingleton.class) {
            if (ObjectUtils.isEmpty(instance)) {
                instance = new DoubleCheckingLockingSingleton();
            }
        }
        return instance;
    }
}

雙重校驗鎖方式實現的單例模式是方法級鎖的優化,減小了部分獲取實例的耗時,同時也知足了懶加載。

5. 靜態內部類單例模式(線程安全)

/**
 * 靜態內部類單例模式(線程安全)
 */
public class StaticInnerSingleton {
    private static class SingletonHolder {
        private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private StaticInnerSingleton(){
    }

    public static final StaticInnerSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

靜態內部類實現的單例模式,既保證了線程安全,也保證了懶加載,同時不會由於加鎖的方式致使性能開銷過大。

這主要是由於 JVM 虛擬機能夠保證多進程併發訪問的正確性,即一個類的構造方法在多線程環境下,能夠被正確加載。

所以,靜態類內部類的實現方式很是推薦使用。

6. CAS「AtomicReference」單例模式(線程安全)

/**
 * CAS「AtomicReference」單例模式(線程安全)
 */
public class CompareAndSwapSingleton {
    private static final AtomicReference<CompareAndSwapSingleton> INSTANCE = new AtomicReference<CompareAndSwapSingleton>();

    private static CompareAndSwapSingleton instance;

    /**
     * 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法
     */
    private CompareAndSwapSingleton() {
    }

    public static final CompareAndSwapSingleton getInstance() {
        for (; ; ) {
            CompareAndSwapSingleton instance = INSTANCE.get();
            if (ObjectUtils.isNotEmpty(instance)) {
                return instance;
            }
            INSTANCE.compareAndSet(null, new CompareAndSwapSingleton());
            return INSTANCE.get();
        }
    }
}

Java 併發庫提供了不少原子類來支持併發訪問的數據安全性:AtomicIntegerAtomicBoolean AtomicLongAtomicReference

AtomicReference 能夠封裝引用一個實例,支持併發訪問,該單例方式就是使用該特色實現。

採用 CAS 方式的優勢就是不須要傳統的加鎖方式來保證線程安全,而是依賴 CAS 的忙等算法,依賴底層硬件的實現,來保證線程安全。相對於其餘鎖的實現沒有線程的切換和阻塞,也就沒有了額外開銷,所以能夠支持較大的併發。

固然,CAS 方式也存在缺點,忙等即若是一直沒有獲取到將會處於死循環中。

7. 枚舉單例模式(線程安全)

/**
 * 枚舉單例(線程安全)
 */
public enum EnumSingleton {
    INSTANCE;

    public void whateverMethod() {
        System.out.println("enum singleton method");
    }
}
約書亞·布洛克(英語:Joshua J. Bloch,1961年8⽉28⽇-),美國著名程序員,《Effective Java》做者。他爲Java平臺設計並實做了許多的功能,曾擔任Google的⾸席Java架構師(Chief Java Architect)。

《Effective Java》做者約書亞·布洛克推薦使用枚舉的方式解決單例模式,這種方式應該是平時最少用到的。

這種方式解決的單例模式的主要問題:線程安全、自由串行化、單一實例。

調用方式

@Test
public void testEnumSingleton()
{
    EnumSingleton.INSTANCE.whateverMethod();
}

這種寫法在功能上與共有域⽅法相近,可是它更簡潔,⽆償地提供了串⾏化機制,絕對防⽌對此實例化,即便是在⾯對複雜的串⾏化或者反射攻擊的時候。雖然這種方式尚未⼴泛採⽤,可是單元素的枚舉類型已經成爲實現單例 的最佳⽅法。

但這種⽅式在存在繼承場景下是不可⽤的。

4、單例模式結構

原型模式-模式結構圖

  1. 單例(Singleton)類聲明瞭一個名爲 getInstance 獲取實例的靜態方法來返回其所屬類的一個相同實例。
  2. 單例的構造模式必須對客戶端(Client)代碼隱藏。調用 獲取實例 方法必須是獲取單例對象的惟一方式。

設計模式並不難學,其自己就是多年經驗提煉出的開發指導思想,關鍵在於多加練習,帶着使用設計模式的思想去優化代碼,就能構建出更合理的代碼。

源碼地址: https://github.com/yiyufxst/d...

參考資料:
小博哥重學設計模式:https://github.com/fuzhengwei...
深刻設計模式:https://refactoringguru.cn/de...

相關文章
相關標籤/搜索