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