小小的單例模式竟然有這么多種寫(xiě)法?
單例模式應(yīng)該是設(shè)計(jì)模式中最容易理解也是用得最多的一種模式了,同時(shí)也是面試的時(shí)候最常被問(wèn)到的模式。
1. 單例模式的定義
單例模式指的是一個(gè)類中在任何情況下都絕對(duì)只有一個(gè)實(shí)例,并且提供一個(gè)全局訪問(wèn)點(diǎn)。
2. 單例模式的應(yīng)用場(chǎng)景
單例模式的應(yīng)用非常廣泛,如數(shù)據(jù)庫(kù)中的連接池、J2EE中的ServletContext和ServletContextConfig、Spring框架中的ApplicationContext等等。然而在Java中,單例模式還可以保證一個(gè)JVM中只存在一個(gè)唯一的實(shí)例。
單例模式的應(yīng)用場(chǎng)景主要有以下幾個(gè)方面:
- 當(dāng)需要頻繁創(chuàng)建一些類的時(shí)候,使用單例可以降低系統(tǒng)的內(nèi)存壓力,減少GC(垃圾回收) ;
- 當(dāng)某些類創(chuàng)建實(shí)例時(shí)候需要占用的資源較多,或者實(shí)例化過(guò)程耗時(shí)比較長(zhǎng),且經(jīng)常使用的情況;
- 當(dāng)存在頻繁訪問(wèn)數(shù)據(jù)庫(kù)或者文件的對(duì)象;
- 當(dāng)對(duì)于一些控制硬件級(jí)別的操作,或者從系統(tǒng)上來(lái)講應(yīng)當(dāng)是單一控制邏輯的操作,是不允許存在多個(gè)實(shí)例的,否則玩完;
3. 單例模式的優(yōu)缺點(diǎn)
3.1 單例模式的優(yōu)點(diǎn)
- 單例模式可以保證內(nèi)存中只有一個(gè)實(shí)例對(duì)象,從而會(huì)減少內(nèi)存的開(kāi)銷(xiāo);
- 單例模式可以避免對(duì)資源的多重占用;
- 單例模式設(shè)置全局訪問(wèn)點(diǎn),可以起到優(yōu)化和共享資源的訪問(wèn)的作用;
3.2 單例模式的缺點(diǎn)
- 擴(kuò)展難, 因?yàn)閱卫J酵ǔJ菦](méi)有接口的啊,如果想要擴(kuò)展,那么你唯一途徑就是修改之前的代碼,所以說(shuō)單例模式違背了開(kāi)閉原則;
- 調(diào)試難,因?yàn)樵诓l(fā)測(cè)試中,單例模式是不利于代碼的調(diào)試的,單例中的代碼沒(méi)有執(zhí)行完,也不能模擬生成一個(gè)新對(duì)象;
- 違背單一職責(zé)原則,因?yàn)閱卫J降臉I(yè)務(wù)代碼通常寫(xiě)在一個(gè)類中,如果功能設(shè)計(jì)不合理,就很容易違背單一職責(zé)原則;
4. 單例模式的實(shí)現(xiàn)方式及其優(yōu)缺點(diǎn)
4.1 單例模式的餓漢式實(shí)現(xiàn)
4.1.1 餓漢式標(biāo)準(zhǔn)寫(xiě)法
Singleton類稱為單例類,通過(guò)內(nèi)部初始化一次 , 隱藏構(gòu)造方法, 并提供一個(gè)全局訪問(wèn)點(diǎn)的方式實(shí)現(xiàn)。
- /**
- * msJava
- *
- * @Description 單例模式的通用寫(xiě)法
- * @Date 2021-01-23
- */
- public class Singleton {
- /**
- * 內(nèi)部初始化一次
- */
- private static final Singleton instance = new Singleton();
- /**
- * 隱藏構(gòu)造方法
- */
- private Singleton() {
- }
- /**
- * 提供一個(gè)全局訪問(wèn)點(diǎn)
- *
- * @return Singleton
- */
- public static Singleton getInstance() {
- return instance;
- }
- }
以上餓漢式單例寫(xiě)法在類的初始化的時(shí)候就會(huì)進(jìn)行初始化操作,并且創(chuàng)建對(duì)象,絕對(duì)的線程安全,因?yàn)榇藭r(shí)線程還沒(méi)有出現(xiàn)就已經(jīng)實(shí)例化了,故不會(huì)存在訪問(wèn)安全的問(wèn)題。
4.1.2 餓漢式靜態(tài)塊機(jī)制寫(xiě)法
餓漢式還有一種實(shí)現(xiàn),那就是靜態(tài)塊機(jī)制,如下代碼所示:
- /**
- * msJava
- *
- * @Description 單例模式 餓漢式靜態(tài)機(jī)制 實(shí)現(xiàn)
- * @Date 2021-01-23
- */
- public class HungryStaticSingleton {
- private static final HungryStaticSingleton hungrySingleton;
- //靜態(tài)代碼塊 類加載的時(shí)候就初始化
- static {
- hungrySingleton=new HungryStaticSingleton();
- }
- /**
- * 私有化構(gòu)造函數(shù)
- */
- private HungryStaticSingleton(){}
- /**
- * 提供一個(gè)全局訪問(wèn)點(diǎn)
- * @return
- */
- public static HungryStaticSingleton getInstance() {
- return hungrySingleton;
- }
- }
我們分析一下這種是寫(xiě)法 ,可以明顯的看到所以對(duì)象是類在加載的時(shí)候就進(jìn)行實(shí)例化了,那么這樣一來(lái),會(huì)導(dǎo)致單例對(duì)象的數(shù)量不確定,從而會(huì)導(dǎo)致系統(tǒng)初始化的時(shí)候就造成大量?jī)?nèi)存浪費(fèi),況且你用不用還不一定,還一直占著空間,俗稱“占著茅坑不拉屎”。
4.2 單例模式的懶漢式實(shí)現(xiàn)
為了解決餓漢式單例寫(xiě)法可能帶來(lái)的內(nèi)存浪費(fèi)問(wèn)題,這里分析一下懶漢式單例的寫(xiě)法。如下代碼所示:
- /**
- * msJava
- *
- * @Description 單例模式 懶漢式單例實(shí)現(xiàn)
- * @Date 2021-01-23
- */
- public class LazySimpleSingleton {
- private static LazySimpleSingleton lazySingleton = null;
- /**
- * 私有化構(gòu)造函數(shù)
- */
- private LazySimpleSingleton() {
- }
- /**
- * 提供一個(gè)全局訪問(wèn)點(diǎn)
- *
- * @return
- */
- public static LazySimpleSingleton getInstance() {
- if (lazySingleton == null) {
- lazySingleton = new LazySimpleSingleton();
- }
- return lazySingleton;
- }
- }
這樣實(shí)現(xiàn)的好處就是只有對(duì)象被使用的時(shí)候才會(huì)進(jìn)行初始化,不會(huì)存在內(nèi)存浪費(fèi)的問(wèn)題,但是它會(huì)在多線程環(huán)境下,存在線程安全問(wèn)題。我們可以利用synchronized關(guān)鍵字將全局訪問(wèn)點(diǎn)方法變成一個(gè)同步方法,這樣就可以解決線程安全的問(wèn)題,代碼如下所示:
- /**
- * msJava
- *
- * @Description 單例模式 懶漢式單例實(shí)現(xiàn) synchronized修飾
- * @Date 2021-01-23
- */
- public class LazySimpleSingleton {
- private static LazySimpleSingleton lazySingleton = null;
- /**
- * 私有化構(gòu)造函數(shù)
- */
- private LazySimpleSingleton() {}
- /**
- * 提供一個(gè)全局訪問(wèn)點(diǎn)
- *
- * @return
- */
- public synchronized static LazySimpleSingleton getInstance() {
- if (lazySingleton == null) {
- lazySingleton = new LazySimpleSingleton();
- }
- return lazySingleton;
- }
- }
但是,這樣雖然解決了線程安全的問(wèn)題,可是如果在線程數(shù)量劇增的情況下,用synchronized加鎖,則會(huì)導(dǎo)致大批線程阻塞,從而驟減系統(tǒng)性能。
4.3 單例模式的雙重檢測(cè)實(shí)現(xiàn)
在上述代碼上進(jìn)一步優(yōu)化,代碼如下所示:
- /**
- * msJava
- *
- * @Description 單例模式 懶漢式-雙重檢測(cè)單例實(shí)現(xiàn)
- * @Date 2021-01-23
- */
- public class LazyDoubleCheckSingleton {
- // volatile 關(guān)鍵字修飾
- private volatile static LazyDoubleCheckSingleton lazySingleton ;
- /**
- * 私有化構(gòu)造函數(shù)
- */
- private LazyDoubleCheckSingleton() {}
- /**
- * 提供一個(gè)全局訪問(wèn)點(diǎn)
- *
- * @return
- */
- public static LazyDoubleCheckSingleton getInstance() {
- // 這里先判斷一下是否阻塞
- if (lazySingleton == null) {
- synchronized (LazyDoubleCheckSingleton.class){
- // 判斷是否需要重新創(chuàng)建實(shí)例
- if (lazySingleton == null) {
- lazySingleton = new LazyDoubleCheckSingleton();
- }
- }
- }
- return lazySingleton;
- }
- }
()方法時(shí),第二個(gè)線程也可以調(diào)用,但是第一個(gè)線程執(zhí)行synchronized時(shí)候,第二個(gè)線程就會(huì)發(fā)現(xiàn)阻塞,但是此時(shí)的阻塞是getInstance()內(nèi)部的阻塞。
4.4 單例模式的靜態(tài)內(nèi)部類實(shí)現(xiàn)
雖然雙重檢測(cè)鎖的單例模式解決了線程安全和性能問(wèn)題,但是畢竟涉及加鎖的操作,多多少少就會(huì)到了性能的影響,下面我們分享一下更加優(yōu)雅的單例模式實(shí)現(xiàn),如下代碼所示:
- /**
- * msJava
- *
- * @Description 單例模式 靜態(tài)內(nèi)部類單例實(shí)現(xiàn)
- * @Date 2021-01-23
- */
- public class LazyStaticInnerClassSingleton {
- // 在構(gòu)造方法里面拋出異常真的合適?
- private LazyStaticInnerClassSingleton(){
- if(LazyHolder.INSTANCE != null){
- throw new RuntimeException("不允許創(chuàng)建多個(gè)實(shí)例");
- }
- }
- // static 保證這個(gè)方法不會(huì)被重寫(xiě) 覆蓋
- private static LazyStaticInnerClassSingleton getInstance(){
- return LazyHolder.INSTANCE;
- }
- // Java 默認(rèn)不會(huì)加載內(nèi)部類
- private static class LazyHolder{
- private static final LazyStaticInnerClassSingleton INSTANCE=new LazyStaticInnerClassSingleton();
- }
- }
5. 總結(jié)
單例模式面試幾乎必備!