前言:要改變命運,首先要改變自己。
一、概述
設計模式代表了最佳的實踐,是一套被反覆使用,多數人知曉,經過分類編目,代碼設計經驗的總結。使用設計模式是爲了重用代碼,讓代碼更容易被人理解,保證代碼可靠性。設計模式提供了軟件開發過程中面臨的一些問題的最佳解決方案,非常重要,主要分爲創建型模式、結構型模式、創建型模式和J2EE 模式。
單例模式是設計模式中最簡單的模式之一。這種類型的設計模式屬於創建型設計模式,它提供了一種最佳創建對象的方式。所謂單例,就是保證整個程序有且僅有一個實例,負責創建自己的對象,同時保證只有一個對象被創建,向整個系統提供這個實例。這個類提供了一種訪問其唯一的對象方式,可以直接訪問,不需要實例化該類的對象。
注意:單例類只有一個實例,必須自己創建自己的唯一實例,給其他所有實例提供這個實例。
特點:
- 1.構造函數私有
- 2.持有自己類型的屬性
- 3.對外提供獲取實例的靜態方法
優缺點:
- 優點:1.在內存中只有一個實例,節省內存空間;避免頻繁創建銷燬對象,提高性能;
- 2.避免對共享資源的多種佔用,簡化訪問,爲全局提供一個訪問點。
- 缺點:1.不適用於變化頻繁的對象;
- 2.沒有接口,不能繼承,與單一職責原則衝突,只關心內部邏輯,而不關心外部變化。
二、實例
單例模式有多種實現方式,如懶漢式,餓漢式,雙重校驗模式,靜態內部類模式,枚舉等。
1.懶漢模式
懶漢模式,顧名思義,比較“懶”,在需要用到的時候檢查實例,有則直接返回,沒有則纔去創建,有線程安全和線程不安全兩種寫方法。
非線程安全:
//懶漢單例模式:非線程安全
public class LazySingleton {
//唯一的實例
private static LazySingleton mInstance;
//私有構造方法,保證自己纔可以創建實例
private LazySingleton() {}
//靜態方法獲取唯一可用的對象
public static LazySingleton getInstance() {
if (mInstance == null) {
mInstance = new LazySingleton();
}
return mInstance;
}
}
這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程,線程不安全的,因爲沒有加鎖synchronized,嚴格意義上來說它不屬於單例,這種方式lazy loading很明顯,不要求線程安全,在多線程不能正常工作。
線程安全:
//懶漢模式:線程安全
public class LazySingleton {
//唯一的實例
private static LazySingleton mInstance;
//私有構造方法,保證自己纔可以創建實例
private LazySingleton() {}
//靜態方法獲取唯一可用的對象,synchronized加鎖保證線程安全
public static synchronized LazySingleton getInstance() {
if (mInstance == null) {//保證單例的唯一性,在沒有的時候纔去創建
mInstance = new LazySingleton();
}
return mInstance;
}
}
加鎖 synchronized這種方式很好具備lazy loading(懶加載),線程安全,能都在多線程中很好的工作,但是效率比較低,絕大部分情況下不需要同步。雙重鎖模式更高效,下面講解到。
2.餓漢模式
餓漢式,顧名思義,比較“餓”,在實例化的時候已經創建了,不管你有沒有用到,都先建好實例再說,好處是沒有加鎖,效率較高,沒有線程安全問題,壞處是容易產生垃圾對象,浪費內存。
//餓漢單例模式
public class HungerSingleton {
//唯一的實例,實例化的時候直接創建
private static HungerSingleton mInstance = new HungerSingleton();
//私有構造方法,保證自己纔可以創建實例
private HungerSingleton() {}
//靜態方法獲取唯一可用的對象
public static HungerSingleton getInstance() {
return mInstance;
}
}
這種方式比較常用,但是極易產生垃圾對象,線程安全,沒有加鎖,效率比較高,就是類加載時就初始化了,浪費內存。它基於classloader機制避免了多線程問題,不過,instance在類加載時就實例化,雖然導致類裝載的方法有很多中,在單例模式中大多數都是調用getInstance(),但是也不能確定有其他的方法(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然是沒有到達lazy loading的效果。
3.雙重鎖模式
雙重檢鎖是結合了懶漢式和餓漢式的優缺點整合而成,安全且在多線程情況下保證高性能。
//雙檢鎖單例模式
public class Singleton {
//唯一的實例
private static Singleton mInstance;
//私有構造方法,保證自己纔可以創建實例
private Singleton() {}
//靜態方法獲取唯一可用的對象
public static Singleton getInstance() {
if (mInstance == null) {//爲了不用每次獲取對象都強制加鎖,提升效率,判斷不存在再加鎖
synchronized (Singleton.class) {//加鎖,爲了線程安全
if (mInstance == null) {//保證單例的唯一性,在沒有的時候纔去創建
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
這種方式採取雙鎖機制,特點是在synchronized內外都加上了一層if條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省內存空間。
4.靜態內部類模式
靜態內部類單例模式其實是餓漢式的變種,利用jvm加載來保證線程安全,並且實現了懶加載,在需要用到的時候纔去創建。
//靜態內部類單例模式
public class StaticSingleton {
//私有構造方法,保證自己纔可以創建實例
private StaticSingleton() {
}
//靜態成員式內部類,該內部類實例與外部類實例沒有綁定的關係,而且只有被調用時纔會裝載,從而實現延遲加載
private static class innerSingleton {
//靜態初始化器,由jvm來保證線程安全
private static final StaticSingleton instance = new StaticSingleton();
}
//靜態方法獲取唯一可用的對象
public static StaticSingleton getInstance() {
return innerSingleton.instance;
}
}
static修飾的靜態內部類,相當於外部類的靜態成員變量,不與外部類實例產生依賴,靜態內部類只可以應用外部類的靜態成員和靜態方法,只有在第一次使用到靜態內部類時纔會被裝載。所以上述的實例instance在使用到時才被初始化。這種方式能到達雙檢鎖一樣的功效,但是實現更簡單,對於靜態域使用初始化,應該使用這種方式,而不是雙檢鎖方式,這種使用於靜態域的情況,雙檢鎖方式可以在實例域延遲初始化使用。
靜態內部類單例模式與餓漢式比較:
靜態內部類單例模式同樣利用了classloader來保證初始化instance只有一個線程,它與餓漢式不同的是,餓漢式只要Singleton類被裝載,instance就會被初始化,沒有達到 lazy loading的效果,而靜態內部類單例模式是Singleton類被裝載了,但是instance不一樣被初始化,因爲靜態內部類innerSingleton沒有被主動使用,而使用通過getInstance()方法顯示去調用,纔會轉載innerSingleton類,從而初始化instance。如果實例化instance很耗資源,所以想讓它延遲加載,不希望它在類實例化時就被初始化,因爲不確定Singleton類還可能在其他地方被主動使用從而被加載,這時候初始化intance很不合適,這種方式比餓漢式就很合理了。
5.枚舉
這種方式沒有被廣泛使用,但是這是單例模式的最佳方法,它更簡潔,避免多線程同步問題,自動支持序列化機制,絕對防止多次實例化。
//枚舉:這種方式沒有被廣泛使用,但是這是單例模式的最佳方法,它更簡潔,避免多線程同步問題,自動支持序列化機制,絕對房子多次實例化。
public enum EnumSingleton {
INSTANCE;
public void getInstance(){
}
}
它在JDK1.5之後纔出現Enum,比較少用到。
我們來總結一下這幾中的用法:
模式 | 說明 | 特點 | |
---|---|---|---|
單例模式: 保證整個程序有且僅有一個實例 |
懶漢式 |
需要用到纔去檢查實例,有則直接返回,無則直接創建 |
Lazy初始化,非線程安全,效率高,沒有加鎖 |
Lazy初始化,線程安全,效率低,加鎖 | |||
餓漢式 | 在類初始化的時候已經創建實例 | 非Lazy初始化,線程安全,效率高,不加鎖 | |
雙檢鎖模式 | 採取雙鎖機制,在synchronized內外都加上了一層if條件判斷 | Lazy初始化,線程安全,效率高,加鎖 | |
靜態內部類模式 | 餓漢式的變種,static修飾的靜態內部類,利用jvm加載來保證線程安全,並且實現了懶加載,需要用到的時纔去創建實例 | Lazy初始化,線程安全,效率高,不加鎖 | |
枚舉 | 特殊數據類型 | 簡潔,避免多線程同步問題,自動支持列化機制,不常用 |
不建議使用第1種懶漢式,建議使用第2種餓漢式。只有在明確實現lazy loading效果時,纔會使用第4種靜態單例模式,如果涉及到反序列化創建對象時,可以嘗試使用第5種枚舉方式,如果有特殊需求,可以使用第3種雙檢鎖模式。
至此,本文結束!
源碼地址:https://github.com/FollowExcellence/JavaDesignMode
請尊重原創者版權,轉載請標明出處:https://blog.csdn.net/m0_37796683/article/details/103203266 謝謝!