做網(wǎng)站前微信朋友圈廣告如何投放
目錄
引言
1. 單例模式的核心思想
2. 單例模式的實現(xiàn)方式
2.1 餓漢式單例
2.2 懶漢式單例
2.3 線程安全的懶漢式單例
2.4 雙重檢查鎖定(Double-Checked Locking)
2.5 靜態(tài)內(nèi)部類實現(xiàn)單例
2.6 枚舉實現(xiàn)單例
3. 單例模式的使用場景
4. 單例模式的優(yōu)缺點
優(yōu)點:
缺點:
5. 總結(jié)
引言
單例模式(Singleton Pattern)是設(shè)計模式中最簡單且最常用的創(chuàng)建型模式之一。它的核心思想是確保一個類只有一個實例,并提供一個全局訪問點來獲取該實例。單例模式在許多場景中非常有用,例如配置管理、線程池、數(shù)據(jù)庫連接池等。
1. 單例模式的核心思想
單例模式的核心思想是:
-
私有化構(gòu)造函數(shù):防止外部通過?
new
?關(guān)鍵字創(chuàng)建實例。 -
提供一個靜態(tài)方法:用于獲取類的唯一實例。
-
確保唯一性:在整個應(yīng)用程序生命周期中,類的實例只有一個。
2. 單例模式的實現(xiàn)方式
單例模式有多種實現(xiàn)方式,每種方式都有其優(yōu)缺點。以下是幾種常見的實現(xiàn)方式:
2.1 餓漢式單例
餓漢式單例在類加載時就創(chuàng)建實例,因此是線程安全的。
public class Singleton {// 在類加載時創(chuàng)建實例private static final Singleton INSTANCE = new Singleton();// 私有化構(gòu)造函數(shù)private Singleton() {}// 提供全局訪問點public static Singleton getInstance() {return INSTANCE;}
}
餓漢式是最簡單的單例模式的寫法,保證了線程的安全,在很長的時間里,我都是餓漢模式來完成單例 的,因為夠簡單,后來才知道餓漢式會有一點小問題,看下面的代碼:
public class Hungry {private byte[] data1 = new byte[1024];private byte[] data2 = new byte[1024];private byte[] data3 = new byte[1024];private byte[] data4 = new byte[1024];private Hungry() {}private final static Hungry hungry = new Hungry();public static Hungry getInstance() {return hungry;}}
?在Hungry類中,我定義了四個byte數(shù)組,當(dāng)代碼一運(yùn)行,這四個數(shù)組就被初始化,并且放入內(nèi)存了,如 果長時間沒有用到getInstance方法,不需要Hungry類的對象,這不是一種浪費(fèi)嗎?我希望的是 只有用 到了 getInstance方法,才會去初始化單例類,才會加載單例類中的數(shù)據(jù)。所以就有了 第二種單例模 式:懶漢式。
優(yōu)點:
-
實現(xiàn)簡單,線程安全。
缺點:
-
如果實例未被使用,會造成資源浪費(fèi)。
2.2 懶漢式單例
懶漢式單例在第一次調(diào)用?getInstance()
?時才創(chuàng)建實例。
public class LazyMan {private LazyMan() {System.out.println(Thread.currentThread().getName()+"Start");}private static LazyMan lazyMan;public static LazyMan getInstance() {if (lazyMan == null) {lazyMan = new LazyMan();}return lazyMan;}// 測試并發(fā)環(huán)境,發(fā)現(xiàn)單例失效public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{LazyMan.getInstance();}).start();}}}
缺點:
-
線程不安全,多個線程可能同時進(jìn)入?
if (instance == null)
?條件,導(dǎo)致創(chuàng)建多個實例。
2.3 線程安全的懶漢式單例
通過在?getInstance()
?方法上加鎖,可以解決懶漢式單例的線程安全問題。
public class LazyMan {private LazyMan() {}private static LazyMan lazyMan;public static LazyMan getInstance() {if (lazyMan == null) {synchronized (LazyMan.class) {if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}}
保證了線程的安全性,又符合了懶加載,只有在用到的時候,才會去初始化,調(diào)用 效率也比較高,但是這種寫法在極端情況還是可能會有一定的問題。因為?:
lazyMan = new LazyMan();
不是原子性操作,至少會經(jīng)過三個步驟:
1. 分配對象內(nèi)存空間
2. 執(zhí)行構(gòu)造方法初始化對象
3. 設(shè)置instance指向剛分配的內(nèi)存地址,此時instance !=null;
由于指令重排,導(dǎo)致A線程執(zhí)行 lazyMan = new LazyMan();的時候,可能先執(zhí)行了第三步(還沒執(zhí)行第 二步),此時線程B又進(jìn)來了,發(fā)現(xiàn)lazyMan已經(jīng)不為空了,直接返回了lazyMan,并且后面使用了返回 的lazyMan,由于線程A還沒有執(zhí)行第二步,導(dǎo)致此時lazyMan還不完整,可能會有一些意想不到的錯 誤,所以就有了下面一種單例模式。
2.4 雙重檢查鎖定(Double-Checked Locking)
雙重檢查鎖定是一種優(yōu)化后的線程安全懶漢式單例實現(xiàn)方式。
這種單例模式只是在上面DCL單例模式增加一個volatile關(guān)鍵字來避免指令重排:
public class LazyMan {private LazyMan() {}private volatile static LazyMan lazyMan;public static LazyMan getInstance() {if (lazyMan == null) {synchronized (LazyMan.class) {if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}
}
優(yōu)點:
-
線程安全,且只有在第一次創(chuàng)建實例時加鎖,性能較好。
注意:
-
必須使用?
volatile
?關(guān)鍵字,防止指令重排序?qū)е碌膯栴}。
2.5 靜態(tài)內(nèi)部類實現(xiàn)單例
靜態(tài)內(nèi)部類實現(xiàn)單例是一種優(yōu)雅且線程安全的方式。
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
優(yōu)點:
-
線程安全,且只有在調(diào)用?
getInstance()
?時才會加載?SingletonHolder
?類,實現(xiàn)懶加載。
2.6 枚舉實現(xiàn)單例
枚舉實現(xiàn)單例是《Effective Java》推薦的方式,它天然支持線程安全和防止反射攻擊。
public enum Singleton {INSTANCE;public void doSomething() {System.out.println("Doing something...");}
}
優(yōu)點:
-
線程安全,代碼簡潔,防止反射和序列化破壞單例。
3. 單例模式的使用場景
單例模式適用于以下場景:
-
全局配置管理:例如讀取配置文件,確保配置信息全局唯一。
-
數(shù)據(jù)庫連接池:確保連接池只有一個實例,避免資源浪費(fèi)。
-
日志管理:確保日志記錄器全局唯一。
-
線程池:確保線程池的唯一性,避免重復(fù)創(chuàng)建線程。
4. 單例模式的優(yōu)缺點
優(yōu)點:
-
節(jié)省資源:避免重復(fù)創(chuàng)建對象,減少內(nèi)存開銷。
-
全局訪問點:方便對唯一實例的管理和訪問。
缺點:
-
擴(kuò)展性差:單例類通常難以擴(kuò)展,因為其構(gòu)造函數(shù)是私有的。
-
違背單一職責(zé)原則:單例類既負(fù)責(zé)創(chuàng)建實例,又負(fù)責(zé)業(yè)務(wù)邏輯。
-
測試?yán)щy:單例類的全局狀態(tài)可能導(dǎo)致測試?yán)щy。
5. 總結(jié)
單例模式是一種簡單但強(qiáng)大的設(shè)計模式,適用于需要全局唯一實例的場景。通過不同的實現(xiàn)方式(如餓漢式、懶漢式、雙重檢查鎖定、靜態(tài)內(nèi)部類、枚舉等),可以滿足不同的需求。
在實際開發(fā)中,應(yīng)根據(jù)具體場景選擇合適的單例實現(xiàn)方式。如果需要懶加載且線程安全,推薦使用靜態(tài)內(nèi)部類或枚舉實現(xiàn);如果需要更高的性能,可以考慮雙重檢查鎖定。