輕松手寫(xiě)單例模式的6種實(shí)現(xiàn)方式脂倦!再也不怕面試官問(wèn)了!

手撕?jiǎn)卫J讲还苁枪P試還是面試元莫,都是高頻題了赖阻。
今天就來(lái)說(shuō)一下單例模式的原理和 6 種實(shí)現(xiàn)方式。

文章目錄:

目錄

一柒竞、單例模式的定義

定義: 確保一個(gè)類只有一個(gè)實(shí)例政供,并提供該實(shí)例的全局訪問(wèn)點(diǎn)。

這樣做的好處是:有些實(shí)例朽基,全局只需要一個(gè)就夠了布隔,使用單例模式就可以避免一個(gè)全局使用的類,頻繁的創(chuàng)建與銷毀稼虎,耗費(fèi)系統(tǒng)資源衅檀。

二、單例模式的設(shè)計(jì)要素

  • 一個(gè)私有構(gòu)造函數(shù) (確保只能單例類自己創(chuàng)建實(shí)例)
  • 一個(gè)私有靜態(tài)變量 (確保只有一個(gè)實(shí)例)
  • 一個(gè)公有靜態(tài)函數(shù) (給使用者提供調(diào)用方法)

簡(jiǎn)單來(lái)說(shuō)就是霎俩,單例類的構(gòu)造方法不讓其他人修改和使用哀军;并且單例類自己只創(chuàng)建一個(gè)實(shí)例,這個(gè)實(shí)例打却,其他人也無(wú)法修改和直接使用杉适;然后單例類提供一個(gè)調(diào)用方法,想用這個(gè)實(shí)例柳击,只能調(diào)用猿推。這樣就確保了全局只創(chuàng)建了一次實(shí)例。

三捌肴、單例模式的6種實(shí)現(xiàn)及各實(shí)現(xiàn)的優(yōu)缺點(diǎn)

(一)懶漢式(線程不安全)

實(shí)現(xiàn):

public class Singleton {
     private static Singleton uniqueInstance;
   
     private Singleton() {
        
    }
    
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

說(shuō)明: 先不創(chuàng)建實(shí)例蹬叭,當(dāng)?shù)谝淮伪徽{(diào)用時(shí),再創(chuàng)建實(shí)例状知,所以被稱為懶漢式秽五。

優(yōu)點(diǎn): 延遲了實(shí)例化,如果不需要使用該類饥悴,就不會(huì)被實(shí)例化坦喘,節(jié)約了系統(tǒng)資源。

缺點(diǎn): 線程不安全铺坞,多線程環(huán)境下起宽,如果多個(gè)線程同時(shí)進(jìn)入了 if (uniqueInstance == null) ,若此時(shí)還未實(shí)例化济榨,也就是uniqueInstance == null,那么就會(huì)有多個(gè)線程執(zhí)行 uniqueInstance = new Singleton(); 绿映,就會(huì)實(shí)例化多個(gè)實(shí)例擒滑;

(二)餓漢式(線程安全)

實(shí)現(xiàn):

public class Singleton {
    
    private static Singleton uniqueInstance = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
    
}

說(shuō)明: 先不管需不需要使用這個(gè)實(shí)例腐晾,直接先實(shí)例化好實(shí)例 (餓死鬼一樣,所以稱為餓漢式)丐一,然后當(dāng)需要使用的時(shí)候藻糖,直接調(diào)方法就可以使用了。

優(yōu)點(diǎn): 提前實(shí)例化好了一個(gè)實(shí)例库车,避免了線程不安全問(wèn)題的出現(xiàn)巨柒。

缺點(diǎn): 直接實(shí)例化好了實(shí)例,不再延遲實(shí)例化柠衍;若系統(tǒng)沒(méi)有使用這個(gè)實(shí)例洋满,或者系統(tǒng)運(yùn)行很久之后才需要使用這個(gè)實(shí)例,都會(huì)操作系統(tǒng)的資源浪費(fèi)珍坊。

(三)懶漢式(線程安全)

實(shí)現(xiàn):

public class Singleton {
    private static Singleton uniqueInstance;
    
    private static singleton() {
    }
   
    private static synchronized Singleton getUinqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
}

說(shuō)明: 實(shí)現(xiàn)和 線程不安全的懶漢式 幾乎一樣牺勾,唯一不同的點(diǎn)是,在get方法上 加了一把 鎖阵漏。如此一來(lái)驻民,多個(gè)線程訪問(wèn),每次只有拿到鎖的的線程能夠進(jìn)入該方法履怯,避免了多線程不安全問(wèn)題的出現(xiàn)回还。

優(yōu)點(diǎn): 延遲實(shí)例化,節(jié)約了資源叹洲,并且是線程安全的柠硕。

缺點(diǎn): 雖然解決了線程安全問(wèn)題,但是性能降低了疹味。因?yàn)榻鼋校词箤?shí)例已經(jīng)實(shí)例化了,既后續(xù)不會(huì)再出現(xiàn)線程安全問(wèn)題了糙捺,但是鎖還在诫咱,每次還是只能拿到鎖的線程進(jìn)入該方法,會(huì)使線程阻塞洪灯,等待時(shí)間過(guò)長(zhǎng)坎缭。

(四)雙重檢查鎖實(shí)現(xiàn)(線程安全)

實(shí)現(xiàn):

public class Singleton {
    
    private volatile static Singleton uniqueInstance;
    
    private Singleton() {
    }
    
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }  
}

說(shuō)明: 雙重檢查數(shù)相當(dāng)于是改進(jìn)了 線程安全的懶漢式。線程安全的懶漢式 的缺點(diǎn)是性能降低了签钩,造成的原因是因?yàn)榧词箤?shí)例已經(jīng)實(shí)例化掏呼,依然每次都會(huì)有鎖。而現(xiàn)在铅檩,我們將鎖的位置變了憎夷,并且多加了一個(gè)檢查。 也就是昧旨,先判斷實(shí)例是否已經(jīng)存在拾给,若已經(jīng)存在了祥得,則不會(huì)執(zhí)行判斷方法內(nèi)的有鎖方法了。 而如果蒋得,還沒(méi)有實(shí)例化的時(shí)候级及,多個(gè)線程進(jìn)去了,也沒(méi)有事额衙,因?yàn)槔锩娴姆椒ㄓ墟i饮焦,只會(huì)讓一個(gè)線程進(jìn)入最內(nèi)層方法并實(shí)例化實(shí)例。如此一來(lái)窍侧,最多最多县踢,也就是第一次實(shí)例化的時(shí)候,會(huì)有線程阻塞的情況疏之,后續(xù)便不會(huì)再有線程阻塞的問(wèn)題殿雪。

為什么使用 volatile 關(guān)鍵字修飾了 uniqueInstance 實(shí)例變量 ?

uniqueInstance = new Singleton(); 這段代碼執(zhí)行時(shí)分為三步:

  1. 為 uniqueInstance 分配內(nèi)存空間
  2. 初始化 uniqueInstance
  3. 將 uniqueInstance 指向分配的內(nèi)存地址

正常的執(zhí)行順序當(dāng)然是 1>2>3 锋爪,但是由于 JVM 具有指令重排的特性丙曙,執(zhí)行順序有可能變成 1>3>2。
單線程環(huán)境時(shí)其骄,指令重排并沒(méi)有什么問(wèn)題亏镰;多線程環(huán)境時(shí),會(huì)導(dǎo)致有些線程可能會(huì)獲取到還沒(méi)初始化的實(shí)例拯爽。
例如:線程A 只執(zhí)行了 1 和 3 索抓,此時(shí)線程B來(lái)調(diào)用 getUniqueInstance(),發(fā)現(xiàn) uniqueInstance 不為空毯炮,便獲取 uniqueInstance 實(shí)例逼肯,但是其實(shí)此時(shí)的 uniqueInstance 還沒(méi)有初始化。

解決辦法就是加一個(gè) volatile 關(guān)鍵字修飾 uniqueInstance 桃煎,volatile 會(huì)禁止 JVM 的指令重排篮幢,就可以保證多線程環(huán)境下的安全運(yùn)行。

優(yōu)點(diǎn): 延遲實(shí)例化为迈,節(jié)約了資源三椿;線程安全;并且相對(duì)于 線程安全的懶漢式葫辐,性能提高了搜锰。

缺點(diǎn): volatile 關(guān)鍵字,對(duì)性能也有一些影響耿战。

(五)靜態(tài)內(nèi)部類實(shí)現(xiàn)(線程安全)

實(shí)現(xiàn):

public class Singleton {
    
    private Singleton() {
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
     
}

說(shuō)明: 首先蛋叼,當(dāng)外部類 Singleton 被加載時(shí),靜態(tài)內(nèi)部類 SingletonHolder 并沒(méi)有被加載進(jìn)內(nèi)存剂陡。當(dāng)調(diào)用 getUniqueInstance() 方法時(shí)鸦列,會(huì)運(yùn)行 return SingletonHolder.INSTANCE; 租冠,觸發(fā)了 SingletonHolder.INSTANCE 鹏倘,此時(shí)靜態(tài)內(nèi)部類 SingletonHolder 才會(huì)被加載進(jìn)內(nèi)存薯嗤,并且初始化 INSTANCE 實(shí)例,而且 JVM 會(huì)確保 INSTANCE 只被實(shí)例化一次纤泵。

優(yōu)點(diǎn): 延遲實(shí)例化骆姐,節(jié)約了資源;且線程安全捏题;性能也提高了玻褪。

(六)枚舉類實(shí)現(xiàn)(線程安全)

實(shí)現(xiàn):

public enum Singleton {
    
    INSTANCE;

    //添加自己需要的操作
    public void doSomeThing() {
    
    }
    
}

說(shuō)明: 默認(rèn)枚舉實(shí)例的創(chuàng)建就是線程安全的,且在任何情況下都是單例公荧。

優(yōu)點(diǎn): 寫(xiě)法簡(jiǎn)單带射,線程安全,天然防止反射和反序列化調(diào)用循狰。

  • 防止反序列化
    序列化:把java對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程窟社;
    反序列化: 通過(guò)這些字節(jié)序列在內(nèi)存中新建java對(duì)象的過(guò)程;
    說(shuō)明: 反序列化 將一個(gè)單例實(shí)例對(duì)象寫(xiě)到磁盤(pán)再讀回來(lái)绪钥,從而獲得了一個(gè)新的實(shí)例灿里。
    我們要防止反序列化,避免得到多個(gè)實(shí)例程腹。
    枚舉類天然防止反序列化匣吊。
    其他單例模式 可以通過(guò) 重寫(xiě) readResolve() 方法,從而防止反序列化寸潦,使實(shí)例唯一重寫(xiě) readResolve() :
private Object readResolve() throws ObjectStreamException{
        return singleton;
}

四色鸳、單例模式的應(yīng)用場(chǎng)景

應(yīng)用場(chǎng)景舉例:

  • 網(wǎng)站計(jì)數(shù)器。
  • 應(yīng)用程序的日志應(yīng)用见转。
  • Web項(xiàng)目中的配置對(duì)象的讀取命雀。
  • 數(shù)據(jù)庫(kù)連接池。
  • 多線程池池户。
  • ......

使用場(chǎng)景總結(jié):

  • 頻繁實(shí)例化然后又銷毀的對(duì)象咏雌,使用單例模式可以提高性能。
  • 經(jīng)常使用的對(duì)象校焦,但實(shí)例化時(shí)耗費(fèi)時(shí)間或者資源多赊抖,如數(shù)據(jù)庫(kù)連接池,使用單例模式寨典,可以提高性能氛雪,降低資源損壞。
  • 使用線程池之類的控制資源時(shí)耸成,使用單例模式报亩,可以方便資源之間的通信浴鸿。

如果文章有任何不對(duì)的地方,歡迎大家指正和交流弦追。

整理分享不易岳链,你的點(diǎn)贊、收藏劲件、關(guān)注掸哑,是對(duì)我最大的支持~

關(guān)注猿兄,獲取更多文章~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末零远,一起剝皮案震驚了整個(gè)濱河市苗分,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牵辣,老刑警劉巖摔癣,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纬向,居然都是意外死亡择浊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)罢猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)近她,“玉大人,你說(shuō)我怎么就攤上這事膳帕≌成樱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵危彩,是天一觀的道長(zhǎng)攒磨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)汤徽,這世上最難降的妖魔是什么娩缰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谒府,結(jié)果婚禮上拼坎,老公的妹妹穿的比我還像新娘。我一直安慰自己完疫,他們只是感情好泰鸡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著壳鹤,像睡著了一般盛龄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天余舶,我揣著相機(jī)與錄音啊鸭,去河邊找鬼。 笑死匿值,一個(gè)胖子當(dāng)著我的面吹牛赠制,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播千扔,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼憎妙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了曲楚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤褥符,失蹤者是張志新(化名)和其女友劉穎龙誊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喷楣,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趟大,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铣焊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逊朽。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曲伊,靈堂內(nèi)的尸體忽然破棺而出叽讳,到底是詐尸還是另有隱情,我是刑警寧澤坟募,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布岛蚤,位于F島的核電站,受9級(jí)特大地震影響懈糯,放射性物質(zhì)發(fā)生泄漏涤妒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一赚哗、第九天 我趴在偏房一處隱蔽的房頂上張望她紫。 院中可真熱鬧,春花似錦屿储、人聲如沸贿讹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衩婚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間助赞,已是汗流浹背买羞。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雹食,地道東北人畜普。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像群叶,于是被迫代替她去往敵國(guó)和親吃挑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容