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