Java中的各種單例模式優(yōu)缺點(diǎn)解析
單例模式
單例模式是一種創(chuàng)建型設(shè)計(jì)模式,其目的是確保類只有一個(gè)實(shí)例,并且提供全局訪問(wèn)點(diǎn)以訪問(wèn)該實(shí)例。
在 Java 中,實(shí)現(xiàn)單例模式有多種方式,下面介紹其中的一些。
餓漢式單例模式
在餓漢式單例模式中,實(shí)例在類加載時(shí)就被創(chuàng)建,因此可以保證實(shí)例的唯一性。該模式的實(shí)現(xiàn)非常簡(jiǎn)單,可以使用靜態(tài)初始化器或者私有構(gòu)造方法來(lái)實(shí)現(xiàn)。例如:
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }
在這個(gè)例子中, INSTANCE 是一個(gè)靜態(tài)常量,它在類加載時(shí)被初始化為 Singleton 類的實(shí)例。getInstance()
方法提供了對(duì)該實(shí)例的全局訪問(wèn)點(diǎn)。
優(yōu)點(diǎn):
- 線程安全,因?yàn)閷?shí)例在類加載的時(shí)候就已經(jīng)創(chuàng)建好了。
- 簡(jiǎn)單易用,沒(méi)有線程同步等復(fù)雜問(wèn)題。
- 線程訪問(wèn)單例實(shí)例的速度比懶漢式單例模式更快。
缺點(diǎn):
- 類加載時(shí)就創(chuàng)建實(shí)例,有可能會(huì)浪費(fèi)資源。
- 如果單例類依賴于其他類,這些依賴的類在類加載時(shí)也會(huì)被創(chuàng)建,
懶漢式單例模式
在懶漢式單例模式中,實(shí)例在第一次使用時(shí)才被創(chuàng)建。
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; } }
在上面的實(shí)現(xiàn)中,instance 是靜態(tài)變量,用于存儲(chǔ)單例對(duì)象。在 getInstance 方法中,如果 instance 為 null,則創(chuàng)建一個(gè)新的 Singleton 對(duì)象,否則直接返回 instance。由于沒(méi)有進(jìn)行同步鎖定,所以線程不安全,可能會(huì)導(dǎo)致并發(fā)創(chuàng)建多個(gè)實(shí)例的問(wèn)題。
優(yōu)點(diǎn):
- 延遲對(duì)象的創(chuàng)建時(shí)間,減少內(nèi)存占用。
- 簡(jiǎn)單易懂,易于實(shí)現(xiàn)。
缺點(diǎn):
- 非線程安全,需要考慮并發(fā)情況。
- 多線程環(huán)境下,需要使用同步鎖或者雙重校驗(yàn)鎖來(lái)保證線程安全,可能會(huì)影響性能。
雙重校驗(yàn)鎖單例模式
“雙重檢查鎖”(Double-Checked Locking) 是懶漢式的一種優(yōu)化,這種實(shí)現(xiàn)方式結(jié)合了懶漢式和餓漢式的優(yōu)點(diǎn),既能夠延遲對(duì)象的創(chuàng)建時(shí)間,又能夠保證線程安全。
public class Singleton { private static volatile Singleton INSTANCE; private Singleton() {} public static Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }
在這個(gè)例子中, INSTANCE 是一個(gè) volatile 的靜態(tài)變量,它在第一次使用時(shí)被創(chuàng)建。getInstance()
方法使用雙重檢查鎖定來(lái)確保 INSTANCE 的唯一性。當(dāng)多個(gè)線程同時(shí)訪問(wèn) getInstance()
方法時(shí),只有第一個(gè)線程會(huì)獲得鎖定并創(chuàng)建實(shí)例。其他線程會(huì)等待鎖定被釋放后再次檢查 INSTANCE 是否為空,從而避免了多次創(chuàng)建實(shí)例的情況。
需要注意的是,使用 volatile 關(guān)鍵字可以確保 INSTANCE 變量的可見(jiàn)性和有序性,從而保證多線程環(huán)境下的正確性。
優(yōu)點(diǎn):
- 延遲加載,只有當(dāng)需要用到實(shí)例時(shí)才會(huì)創(chuàng)建,節(jié)省了系統(tǒng)資源。
- 線程安全,適合高并發(fā)場(chǎng)景。
缺點(diǎn):
- 實(shí)現(xiàn)復(fù)雜,容易出現(xiàn)問(wèn)題。
- 由于 Java 內(nèi)存模型的限制,可能會(huì)出現(xiàn)指令重排的問(wèn)題,需要使用 volatile 關(guān)鍵字來(lái)解決。
枚舉單例模式
枚舉單例模式是一種比較新的單例模式實(shí)現(xiàn)方式,它在 Java 5 中引入。它利用了 Java 中枚舉類型的特性來(lái)實(shí)現(xiàn)單例模式,枚舉類型的每個(gè)枚舉常量都是單例的。
public enum Singleton { INSTANCE; public void doSomething() { // do something... } }
在這個(gè)例子中,枚舉類型 Singleton 只有一個(gè)枚舉常量 INSTANCE ,該常量在枚舉類型初始化時(shí)被創(chuàng)建。由于枚舉類型的實(shí)例在 Java 虛擬機(jī)中是唯一的,因此枚舉常量也是單例的。我們可以通過(guò) Singleton.INSTANCE
來(lái)訪問(wèn)該實(shí)例,并調(diào)用 doSomething()
方法。
優(yōu)點(diǎn):
- 簡(jiǎn)單明了: 枚舉單例模式的實(shí)現(xiàn)非常簡(jiǎn)單,而且可以保證線程安全和實(shí)例的唯一性。
- 序列化安全: 由于枚舉實(shí)例在 JVM 中是唯一的,因此可以保證序列化和反序列化的安全性。
- 防止反射攻擊: 枚舉實(shí)例在 JVM 中是唯一的,因此可以避免反射攻擊。
缺點(diǎn):
- 不支持延遲加載 ,因?yàn)槊杜e類型的實(shí)例在類加載時(shí)就已經(jīng)被創(chuàng)建了。
- 不能繼承其他類 ,因?yàn)槊杜e類型默認(rèn)繼承了Enum類。
靜態(tài)內(nèi)部類單例模式
靜態(tài)內(nèi)部類單例模式是一種優(yōu)雅而簡(jiǎn)潔的實(shí)現(xiàn)方式。它利用了 Java 類加載器的機(jī)制來(lái)保證實(shí)例的唯一性,并避免了餓漢式單例模式的缺點(diǎn)。
public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
在這個(gè)例子中, Singleton 類的構(gòu)造方法是私有的,只能在 Singleton 類的內(nèi)部進(jìn)行調(diào)用。 SingletonHolder 類是 Singleton 類的一個(gè)靜態(tài)內(nèi)部類,它在 Singleton 類被加載時(shí)并不會(huì)立即被加載,而是在第一次調(diào)用 Singleton.getInstance()
方法時(shí)才會(huì)被加載,從而實(shí)現(xiàn)了延遲加載。
由于靜態(tài)內(nèi)部類 SingletonHolder 只會(huì)被加載一次,因此 INSTANCE 實(shí)例也只會(huì)被創(chuàng)建一次,從而保證了實(shí)例的唯一性。總的來(lái)說(shuō),靜態(tài)內(nèi)部類單例模式是一種比較優(yōu)秀的單例模式實(shí)現(xiàn)方式,它兼顧了線程安全、懶加載和高效等特點(diǎn),是一種值得推薦的單例模式實(shí)現(xiàn)方式。
優(yōu)點(diǎn):
- 線程安全: 靜態(tài)內(nèi)部類只會(huì)被加載一次,因此可以保證單例對(duì)象的線程安全性;
- 懶加載:靜態(tài)內(nèi)部類只有在被調(diào)用時(shí)才會(huì)被加載,從而實(shí)現(xiàn)了懶加載的效果;
- 高效:靜態(tài)內(nèi)部類單例模式?jīng)]有加鎖,所以性能比懶漢式和餓漢式都要高;
- 簡(jiǎn)單: 靜態(tài)內(nèi)部類單例模式的實(shí)現(xiàn)比較簡(jiǎn)單,代碼量較少。
缺點(diǎn):
- 不易理解: 相比于餓漢式和懶漢式,靜態(tài)內(nèi)部類單例模式的實(shí)現(xiàn)方式可能不太容易理解;
- 無(wú)法傳遞參數(shù): 靜態(tài)內(nèi)部類單例模式的構(gòu)造函數(shù)是私有的,無(wú)法傳遞參數(shù)。如果需要傳遞參數(shù),需要使用其他方式實(shí)現(xiàn)。
注冊(cè)式單例模式
注冊(cè)式單例模式是一種靈活而可擴(kuò)展的實(shí)現(xiàn)方式。在該模式中,單例實(shí)例被注冊(cè)到一個(gè)全局的注冊(cè)表中,可以實(shí)現(xiàn)對(duì)象的統(tǒng)一管理和獲取,但需要注意容器的生命周期和線程安全問(wèn)題。
下面是一個(gè)使用 ConcurrentHashMap 實(shí)現(xiàn)注冊(cè)式單例模式的例子:
public class Singleton { private static Map<String, Singleton> instances = new ConcurrentHashMap<>(); static { instances.put(Singleton.class.getName(), new Singleton()); } private Singleton() {} public static Singleton getInstance() { return instances.get(Singleton.class.getName()); } public static void register(String key, Singleton instance) { instances.put(key, instance); } public static Singleton getRegisteredInstance(String key) { return instances.get(key); } }
Spring 框架中的 Bean 注冊(cè)機(jī)制使用的是注冊(cè)式單例模式。在 Spring 中, Bean 的注冊(cè)是通過(guò) BeanDefinitionRegistry 接口來(lái)完成的,而 BeanDefinitionRegistry 接口的實(shí)現(xiàn)類包括了 DefaultListableBeanFactory 和 GenericApplicationContext 等。這些實(shí)現(xiàn)類在內(nèi)部都使用了類似于注冊(cè)式單例模式的方式來(lái)注冊(cè)和管理 Bean 。
具體來(lái)說(shuō), Spring 在注冊(cè) Bean 時(shí),會(huì)將 Bean 的定義信息封裝成一個(gè) BeanDefinition 對(duì)象,并將其注冊(cè)到一個(gè)全局的 BeanFactory 中,以供后續(xù)的使用。在注冊(cè) Bean 的過(guò)程中, Spring 會(huì)根據(jù) BeanDefinition 中的配置信息來(lái)創(chuàng)建相應(yīng)的 Bean 實(shí)例,同時(shí)還會(huì)對(duì)其進(jìn)行依賴注入、生命周期管理等操作。
需要注意的是,Spring 的 Bean 注冊(cè)機(jī)制中,雖然使用了類似于注冊(cè)式單例模式的方式來(lái)管理 Bean ,但它并不是一個(gè)完全的單例模式實(shí)現(xiàn)。在 Spring 中, Bean 的單例性是在運(yùn)行時(shí)動(dòng)態(tài)實(shí)現(xiàn)的,而不是在編譯期就確定的。也就是說(shuō),如果在 BeanDefinition 中將 scope 屬性設(shè)置為 prototype ,那么每次獲取該 Bean 實(shí)例時(shí)都會(huì)創(chuàng)建一個(gè)新的對(duì)象,而不是返回同一個(gè)單例實(shí)例。
優(yōu)點(diǎn):
- 可以管理多個(gè)單例實(shí)例,可以通過(guò)名稱或者其他方式來(lái)獲取實(shí)例。
- 避免了全局變量帶來(lái)的問(wèn)題,比如命名沖突、不同作用域訪問(wèn)困難等。
缺點(diǎn):
- 容易造成內(nèi)存泄漏,因?yàn)閱卫龑?shí)例不會(huì)被釋放。
- 可能會(huì)造成重復(fù)創(chuàng)建對(duì)象的問(wèn)題。
ThreadLocal單例模式
ThreadLocal單例模式這種實(shí)現(xiàn)方式將單例對(duì)象存儲(chǔ)在ThreadLocal中,每個(gè)線程都有自己的單例對(duì)象副本,保證了線程安全。
public class Singleton { private static final ThreadLocal<Singleton> singletonThreadLocal = new ThreadLocal<Singleton>() { @Override protected Singleton initialValue() { return new Singleton(); } }; private Singleton() {} public static Singleton getInstance() { return singletonThreadLocal.get(); } }
在這個(gè)示例中,每個(gè)線程都有自己的 Singleton 對(duì)象副本,使用 ThreadLocal 可以避免線程安全問(wèn)題。
優(yōu)點(diǎn):
- 每個(gè)線程都有自己的單例對(duì)象副本,線程安全。
- 可以避免鎖競(jìng)爭(zhēng),提高性能。
缺點(diǎn):
- 可能會(huì)導(dǎo)致內(nèi)存泄漏問(wèn)題,需要注意對(duì)象的生命周期。
CAS單例模式
CAS單例模式這種實(shí)現(xiàn)方式利用了CAS原子操作的特性,可以保證線程安全,但需要注意性能問(wèn)題。
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { Singleton temp = new Singleton(); if (compareAndSetInstance(null, temp)) { instance = temp; } } } } return instance; } private static boolean compareAndSetInstance(Singleton expect, Singleton update) { return Unsafe.getUnsafe().compareAndSwapObject(Singleton.class, Unsafe.objectFieldOffset(Singleton.class, "instance"), expect, update); } }
在這個(gè)示例中,使用了 volatile 和 CAS 原子操作來(lái)保證線程安全,同時(shí)實(shí)現(xiàn)了懶加載。需要注意的是,使用 Unsafe 類需要特殊權(quán)限,并且 CAS 實(shí)現(xiàn)的復(fù)雜度比較高,適用于高并發(fā)場(chǎng)景。
優(yōu)點(diǎn):
- 線程安全,高并發(fā)場(chǎng)景性能較好。
缺點(diǎn):
- 實(shí)現(xiàn)復(fù)雜,需要理解 CAS 原子操作。
- 需要使用 Unsafe 類,需要特殊權(quán)限。
- 適用于高并發(fā)場(chǎng)景,對(duì)于低并發(fā)場(chǎng)景可能沒(méi)有明顯的性能優(yōu)勢(shì)。
總結(jié)
總之,單例模式是一種非常常用的設(shè)計(jì)模式,可以確保類只有一個(gè)實(shí)例,并提供全局訪問(wèn)點(diǎn)以訪問(wèn)該實(shí)例。在 Java 中,有多種方式可以實(shí)現(xiàn)單例模式,開(kāi)發(fā)者可以根據(jù)實(shí)際需要選擇適合自己的實(shí)現(xiàn)方式。
到此這篇關(guān)于Java中的各種單例模式優(yōu)缺點(diǎn)解析的文章就介紹到這了,更多相關(guān)Java單例模式解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python的地形三維可視化Matplotlib和gdal使用實(shí)例
這篇文章主要介紹了Python的地形三維可視化Matplotlib和gdal使用實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以了解下。2017-12-12opencv 實(shí)現(xiàn)特定顏色線條提取與定位操作
這篇文章主要介紹了opencv 實(shí)現(xiàn)特定顏色線條提取與定位操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06Django與AJAX實(shí)現(xiàn)網(wǎng)頁(yè)動(dòng)態(tài)數(shù)據(jù)顯示的示例代碼
這篇文章主要介紹了Django與AJAX實(shí)現(xiàn)網(wǎng)頁(yè)動(dòng)態(tài)數(shù)據(jù)顯示的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02pyecharts如何實(shí)現(xiàn)顯示數(shù)據(jù)為百分比的柱狀圖
這篇文章主要介紹了pyecharts如何實(shí)現(xiàn)顯示數(shù)據(jù)為百分比的柱狀圖,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11使用Python批量生成PPT版榮譽(yù)證書(shū)的示例代碼
使用Python處理PPT文件通常需要使用第三方庫(kù)來(lái)簡(jiǎn)化對(duì)PPT文件的讀取、寫(xiě)入和修改操作,本文將給大家介紹一個(gè)小案例,使用Python批量生成PPT版榮譽(yù)證書(shū),感興趣的同學(xué)跟著小編一起來(lái)看看吧2023-08-08如何使用draw.io插件在vscode中一體化導(dǎo)出高質(zhì)量圖片
這篇文章主要介紹了draw.io插件在vscode中一體化導(dǎo)出高質(zhì)量圖片需要的工具是vscode,?draw.io擴(kuò)展,draw.io桌面版?、python,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒,需要的朋友可以參考下2022-08-08Python中bytes和str的區(qū)別與聯(lián)系詳解
Python3最重要的新特性之一是對(duì)字符串和二進(jìn)制數(shù)據(jù)流做了明確的區(qū),下面這篇文章主要給大家介紹了關(guān)于Python中bytes和str區(qū)別與聯(lián)系的相關(guān)資料,需要的朋友可以參考下2022-05-05Python3的unicode編碼轉(zhuǎn)換成中文的問(wèn)題及解決方案
這篇文章主要介紹了Python3的unicode編碼轉(zhuǎn)換成中文的問(wèn)題及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12python實(shí)現(xiàn)代碼統(tǒng)計(jì)器
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)代碼統(tǒng)計(jì)器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09