只一篇就夠了·設(shè)計(jì)模式(5) - 單例模式

單例模式(Singleton Pattern)確保一個類只有一個實(shí)例,并且提供一個全局的訪問径玖。

單例模式隨處可見痴脾,比如線程池緩存梳星、對話框赞赖、日志對象等,這些時(shí)候如果制造出多個實(shí)例冤灾,程序運(yùn)行就會出現(xiàn)預(yù)期之外的情況前域。
這里可能有疑問,我用全局靜態(tài)變量也能做到一個類只有一個實(shí)例韵吨,為什么要引入這樣一個設(shè)計(jì)模式呢匿垄?原因其實(shí)很簡單,全局靜態(tài)變量會造成資源浪費(fèi):假設(shè)這個類非常消耗資源归粉,程序在運(yùn)行過程中浪蹂,不是每一次都用到這個類垮耳,那就是極大的浪費(fèi)仿野。

類圖

類圖不是目的曾棕,僅僅幫助理解

[圖片上傳失敗...(image-1fb5c0-1527174223215)]

單例模式的類圖很簡單序苏,只有一個類喉前,有一個代表自己實(shí)例的instance變量佳吞,還有一個提供全局訪問的靜態(tài)方法getInstance()贮勃。

以下的代碼和思路是針對Java語言

單例類型

單例模式分為懶漢式和餓漢式童擎,區(qū)別在于實(shí)例化單例對象的時(shí)機(jī)滴劲。

懶漢式

在懶漢式單例模式實(shí)現(xiàn)中,不管單例是否用到顾复,都會實(shí)例化一個單例對象班挖。典型的寫法如下:

/**
 * 單例
 * Created by Carlton on 2016/11/21.
 */
class Singleton private constructor()
{
    companion object
    {
        private val instance = Singleton()
        fun instance() = instance
    }
}

因?yàn)?code>Kotlin和Java在靜態(tài)語法上的不一致,后面的代碼都用Java來實(shí)現(xiàn)方便理解

/**
 * 單例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {
        
    }

    private static Singleton instance = new Singleton();
    public static Singleton instance()
    {
        return instance;
    }
}

餓漢式非常簡單芯砸,也不會出現(xiàn)資源占用之外的其他問題萧芙,就不多說给梅。

飽漢式、常規(guī)方法

飽漢式也就是常規(guī)實(shí)現(xiàn)方式比較復(fù)雜双揪,原因是我們用到類實(shí)例的時(shí)候才會去實(shí)例化动羽,這中間會出現(xiàn)各種各樣的情況。

介紹了兩種實(shí)現(xiàn)方式渔期,接下來我們實(shí)現(xiàn)一個常規(guī)的單例模式:

/**
 * 單例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {

    }

    private static Singleton instance = null;
    public static Singleton instance()
    {
        if(instance == null)
        {
            // 1
            instance = new Singleton();
        }
        return instance;
    }
}

在客戶端獲取單例的時(shí)候运吓,檢查對象是否是null如果是,則實(shí)例化一個疯趟,如果不是則直接返回已有的對象拘哨,如果在單線程的情況下,確實(shí)如此信峻,現(xiàn)在如果倦青,兩個或者兩個以上的線程就有問題了:

  • 如果兩個線程都到1這個位置
  • 那么現(xiàn)在的情況就是if (instance == null)判斷的時(shí)候兩個線程都通過了
  • 這個時(shí)候instance會實(shí)例化兩次,這兩個線程拿到的不是同一個實(shí)例

怎么解決這個問題呢站欺?不慌解決姨夹,先看看一下雙重驗(yàn)證和volatile

雙重檢查和volatile

如果加上線程鎖,好像問題就解決了矾策,先看看加線程鎖怎么寫:

/**
 * 單例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {

    }

    private static Singleton instance = null;
    public static Singleton instance()
    {
        if(instance == null)
        {
            // 1
            synchronized (Singleton.class)
            {
                // 2
                if(instance == null)
                { // 3
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

現(xiàn)在看看多線程的情況程序會出現(xiàn)什么問題:

  • 如果有兩個線程都到了1
  • 因?yàn)橥芥i的原因磷账,只有一個線程可以先進(jìn)入到2
  • 當(dāng)?shù)谝粋€線程進(jìn)入3實(shí)例化一個instance后,第二個線程進(jìn)入判斷的時(shí)候贾虽,就不會進(jìn)入3
    這就是雙重驗(yàn)證逃糟,在C/C++中,這樣做是沒有問題的蓬豁,但是:雙重檢查對Java語言編譯器不成立绰咽!原因在于,Java編譯器中地粪,Singleton類的初始化與instance變量賦值的順序不可預(yù)料取募,如果一個線程在沒有同步化的條件下讀取instance引用,并調(diào)用這個對象的方法的話蟆技,可能會發(fā)現(xiàn)對象的初始化過程還沒有完成玩敏,從而造成崩潰。

可能有人會覺得volatile可以解決問題质礼,修改變量申明:

private static volatile Singleton instance = null;

先看看volatile是什么旺聚?

volatile變量具有synchronized的可見性特性,但是不具備原子特性眶蕉。這就是說線程能夠自動發(fā)現(xiàn)volatile變量的最新值砰粹。volatile變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當(dāng)前值與修改后值之間沒有約束造挽。

通過這個描述知道volatile是一個輕量級的線程同步碱璃,之前出現(xiàn)的問題在于線程沒有同步化的條件下讀取instance弄痹,現(xiàn)在加上volatile問題就解決了。但是:JDK1.5之前厘贼,這樣使用雙重檢查還是有問題界酒。

Java中如何正確的實(shí)現(xiàn)單例模式

說了這么多,如何才能正確實(shí)現(xiàn)單例模式呢嘴秸?

  • 使用餓漢式
  • JDK1.5以后使用帶volatile修飾的雙重檢查
  • 同步鎖加到方法上:
/**
 * 單例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
        private static volatile Singleton instance = null;
        private Singleton()
        {
        }
        public static synchronized Singleton instance()
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
        return instance;
        }
}

多說幾句毁欣,如果把同步鎖加到方法上面,代表這個方法同一時(shí)間只有一個線程能夠進(jìn)入方法岳掐,這個時(shí)候后面的線程進(jìn)入就會正常的直接返回instance實(shí)例凭疮。

總結(jié)

單例模式在思路上是很簡單的模式,也就不提供例子串述,單例模式還有很多單例模式的變種执解,但是核心沒變:一個類只有一個實(shí)例;這個實(shí)例由自己來實(shí)例化纲酗;單例模式?jīng)]有提供公共的構(gòu)造函數(shù)衰腌,所以其他類不能對其實(shí)例化。需要注意的是觅赊,這個模式的復(fù)雜點(diǎn)在于實(shí)現(xiàn)方式右蕊,如何才能保證在各種情況下只有一個類實(shí)例才是關(guān)鍵點(diǎn)。

??查看更多??

不登高山吮螺,不知天之高也饶囚;不臨深溪,不知地之厚也
感謝指點(diǎn)鸠补、交流萝风、喜歡

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市紫岩,隨后出現(xiàn)的幾起案子规惰,更是在濱河造成了極大的恐慌,老刑警劉巖泉蝌,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歇万,死亡現(xiàn)場離奇詭異,居然都是意外死亡梨与,警方通過查閱死者的電腦和手機(jī)堕花,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門文狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粥鞋,“玉大人瞄崇,你說我怎么就攤上這事呻粹『韭” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵等浊,是天一觀的道長腮郊。 經(jīng)常有香客問我,道長筹燕,這世上最難降的妖魔是什么轧飞? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮撒踪,結(jié)果婚禮上过咬,老公的妹妹穿的比我還像新娘。我一直安慰自己制妄,他們只是感情好掸绞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耕捞,像睡著了一般衔掸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俺抽,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天敞映,我揣著相機(jī)與錄音,去河邊找鬼凌埂。 笑死驱显,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞳抓。 我是一名探鬼主播埃疫,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孩哑!你這毒婦竟也來了栓霜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤横蜒,失蹤者是張志新(化名)和其女友劉穎胳蛮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丛晌,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仅炊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澎蛛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抚垄。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呆馁,到底是詐尸還是另有隱情桐经,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布浙滤,位于F島的核電站阴挣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏纺腊。R本人自食惡果不足惜畔咧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揖膜。 院中可真熱鬧盒卸,春花似錦、人聲如沸次氨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煮寡。三九已至虹蓄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幸撕,已是汗流浹背薇组。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坐儿,地道東北人律胀。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像貌矿,于是被迫代替她去往敵國和親炭菌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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