設(shè)計模式之單例模式

前言

本篇文章主要介紹的是設(shè)計模式中的單例模式的實現(xiàn)方式犀盟。

什么是設(shè)計模式

設(shè)計模式其實就是前輩們長時間的試驗和錯誤總結(jié)出來的蹈胡,針對軟件開發(fā)過程中面臨的一般問題的解決方案。

設(shè)計模式分類

根據(jù)其目的(模式是用來做什么的)可分為創(chuàng)建型自沧,結(jié)構(gòu)型和行為型三種:
? 創(chuàng)建型模式主要用于創(chuàng)建對象昆汹。
? 結(jié)構(gòu)型模式主要用于處理類或?qū)ο蟮慕M合明刷。
? 行為型模式主要用于描述對類或?qū)ο笤鯓咏换ズ驮鯓臃峙渎氊煛?/p>

單例模式

單例模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式满粗。這種模式保證一個系統(tǒng)中的某個類只有一個能夠被外界訪問的實例辈末。

單例模式的使用場景

在程序中比較常用的是數(shù)據(jù)庫連接池、線程池、日志對象等等挤聘。

單例模式的實現(xiàn)

單例模式的實現(xiàn)有5種方式轰枝,分別是懶漢式、餓漢式组去、雙重鎖鞍陨、靜態(tài)內(nèi)部類、枚舉从隆。

1.懶漢式

這種方式是最基本的實現(xiàn)方式诚撵,但是不支持多線程,線程不安全键闺。

實現(xiàn):定義一個私有的構(gòu)造方法寿烟,定義一個該類靜態(tài)私有的變量,然后定義一個公共的靜態(tài)方法辛燥,在靜態(tài)方法內(nèi)對變量的值進行空判斷筛武,不為空直接返回,如果為空重新構(gòu)建并賦值給改該變量挎塌。

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

測試:

public class SingletonTest {
    public static void main(String[] args) {
        System.out.println(Singleton.getInstance()==Singleton.getInstance());
    }
}

這里輸出的是true徘六,可以看到兩次獲取的實例其實是同一個。

采用多線程方式:

public class SingletonTest {
    public static void main(String[] args) {
        new Thread(new SingletonThread()).start();
        new Thread(new SingletonThread()).start();
    }
}
class SingletonThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Singleton.getInstance().hashCode());
    }
}

此時兩次輸出的哈希值時而相同時而不同榴都,出現(xiàn)線程不安全問題硕噩。

這種方式可以在靜態(tài)方法的方法聲明上加synchronized關(guān)鍵字來確保線程安全,但是效率低下缭贡。

2.餓漢式

這種方式?jīng)]有加鎖炉擅,所以效率會提高。雖然沒有加鎖阳惹,但是也是線程安全的谍失,這是因為它在類加載時就初始化了,而一個類在整個生命周期中只會被加載一次莹汤,因此該單例類只會創(chuàng)建一個實例快鱼。所以餓漢式天生就是線程安全的。但也正是因為它在類加載的時候就初始化了纲岭,會一直占用內(nèi)存抹竹,導(dǎo)致內(nèi)存浪費。

定義一個私有的構(gòu)造方法止潮,并將自身的實例對象設(shè)置為一個靜態(tài)私有屬性,然后通過公共的靜態(tài)方法調(diào)用返回實例窃判。

public class Singleton {
    private static Singleton singleton  = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }
}

測試:和懶漢式的測試代碼一樣,普通方式獲取的兩個實例是同一個喇闸,輸出true袄琳,多線程方式獲取的哈希值是一樣的询件。

3.雙重鎖

定義一個私有構(gòu)造方法,通過volatile定義靜態(tài)私有變量唆樊,保證了該變量的可見性宛琅,然后定義一個共有的靜態(tài)方法,第一次對該對象實例化時與否判斷逗旁,不為空直接返回嘿辟,提升效率;然后使用synchronized 進行同步代碼塊片效,防止對象未初始化時红伦,在多線程訪問該對象在第一次創(chuàng)建后,再次重復(fù)的被創(chuàng)建堤舒;然后第二次對該對象實例化時與否判斷,如果未初始化哺呜,則初始化舌缤,否則直接返回該實例。

第一次判空是為了提升效率某残,只有第一次實例化的時候需要加鎖国撵,而不是每次請求都加鎖
第二次是為了進行同步,避免多線程問題玻墅。

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) { //#1
            synchronized (Singleton.class){ //#2
                if (singleton == null) { //#3
                    singleton = new Singleton(); //#4
                    System.out.println(Thread.currentThread().getName() + ": singleton is initalized...");//#5.1
                } else {
                    System.out.println(Thread.currentThread().getName() + ": singleton is not null now...");//#5.2
                }
            }
        }
        return singleton;
    }
}

雙重鎖這種方式實現(xiàn)單例的關(guān)鍵點在于兩次判空介牙、加鎖、以及volatile關(guān)鍵字澳厢,這里解釋一下volatile關(guān)鍵字在這種方式實現(xiàn)單例起到的作用环础。

volatile有兩個特性
可見性:證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值剩拢,這新值對其他線程來說是立即可見的线得。
有序性:禁止進行指令重排序。

假設(shè)不加volatile關(guān)鍵字徐伐,這段代碼可能輸出的是
thread1: uniqueInstance is initalized...
thread2: uniqueInstance is initalized...

過程分析:
1.thread1進入#1贯钩,獲取到singleton為空,此時thread1讓出CPU資源給thread2办素,thread1進入#1角雷,卻在#2外等待。
2.thread2進入#1性穿,獲取到singleton為空勺三,此時thread2讓出CPU資源給thread1,
thread2進入#1需曾,卻在#2外等待檩咱。
3.thread1會依次執(zhí)行#2揭措,#3,#4刻蚯,#5.1绊含,最終在thread2里面實例化了singleton。thread1執(zhí)行完畢讓出CPU資源給thread2炊汹。
4.thread2接著#1跑下去躬充,跑到#3的時候,由于#1里面拿到的singleton還是空(并沒有及時從thread1里面拿到最新的)讨便,所以thread2仍然會執(zhí)行#4充甚,#5.1
5.最后在thread1和thread2都實例化了singleton。

這樣的話霸褒,singleton實例化了兩次伴找。而volatile關(guān)鍵字修飾變量的作用就是讓第四步中thread2及時拿到thread1更新的的singleton,使它最后執(zhí)行#5.2,這里利用的就是可見性废菱。

volatile使singleton重排序被禁止技矮,所有的寫(write)操作都將發(fā)生在讀(read)操作之前。也就確保了thread1的實例化是發(fā)生在thread2第二次判空之前的殊轴。

當然衰倦,這只是一種假設(shè)的情況,沒有重現(xiàn)過旁理,太難模擬了樊零,但是確實存在。

4.靜態(tài)內(nèi)部類

這種方式也是利用了類加載機制孽文,只不過它不像餓漢式一樣驻襟,Singleton類被加載就實例化,這樣就沒有達到懶加載的效果芋哭。外部類加載時并不需要立即加載內(nèi)部類塑悼,內(nèi)部類不被加載則不去初始化instance,因此不占內(nèi)存楷掉。而靜態(tài)內(nèi)部類實現(xiàn)單例保證線程安全厢蒜,是由JVM決定的。

public class Singleton {
    private Singleton() {}

    private static class SingletonInner {
        private static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInner.singleton;
    }
}

5.枚舉

枚舉是JDK1.5之后的特性烹植。無償提供序列化機制斑鸦,防止多次實例化。

public enum Singleton {  
    INSTANCE;  
}

測試:輸出的哈希值都是一樣的草雕。

public class SingletonTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new SingletonThread()).start();
        }

    }
}

class SingletonThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Singleton.INSTANCE.hashCode());
    }
}

PS:第2.3.4種測試代碼和第一種是一樣的巷屿。

總結(jié):
一般情況下使用餓漢式;如果要求實現(xiàn)懶加載墩虹,則使用靜態(tài)內(nèi)部類嘱巾;如果涉及到反序列化創(chuàng)建對象時憨琳,可以嘗試使用枚舉方式。

CSDN:https://blog.csdn.net/qq_27682773
簡書:http://www.reibang.com/u/e99381e6886e
博客園:https://www.cnblogs.com/lixianguo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旬昭,一起剝皮案震驚了整個濱河市篙螟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌问拘,老刑警劉巖遍略,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異骤坐,居然都是意外死亡绪杏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門纽绍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕾久,“玉大人,你說我怎么就攤上這事拌夏∩” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵辖佣,是天一觀的道長霹抛。 經(jīng)常有香客問我搓逾,道長卷谈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任霞篡,我火速辦了婚禮世蔗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朗兵。我一直安慰自己污淋,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布余掖。 她就那樣靜靜地躺著寸爆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盐欺。 梳的紋絲不亂的頭發(fā)上赁豆,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音冗美,去河邊找鬼魔种。 笑死,一個胖子當著我的面吹牛粉洼,可吹牛的內(nèi)容都是我干的节预。 我是一名探鬼主播叶摄,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼安拟!你這毒婦竟也來了蛤吓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤去扣,失蹤者是張志新(化名)和其女友劉穎柱衔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愉棱,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡唆铐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奔滑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艾岂。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朋其,靈堂內(nèi)的尸體忽然破棺而出王浴,到底是詐尸還是另有隱情,我是刑警寧澤梅猿,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布氓辣,位于F島的核電站,受9級特大地震影響袱蚓,放射性物質(zhì)發(fā)生泄漏钞啸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一喇潘、第九天 我趴在偏房一處隱蔽的房頂上張望体斩。 院中可真熱鬧,春花似錦颖低、人聲如沸絮吵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹬敲。三九已至,卻和暖如春莺戒,著一層夾襖步出監(jiān)牢的瞬間伴嗡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工脏毯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留闹究,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓食店,卻偏偏與公主長得像渣淤,于是被迫代替她去往敵國和親赏寇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

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

  • 單例設(shè)計模式全解析 在學(xué)習(xí)設(shè)計模式時价认,單例設(shè)計模式應(yīng)該是學(xué)習(xí)的第一個設(shè)計模式嗅定,單例設(shè)計模式也是“公認”最簡單的設(shè)計...
    WekingZhang閱讀 301評論 0 1
  • 聲明:原創(chuàng)作品,轉(zhuǎn)載請注明出處http://www.reibang.com/p/b99e870f4ce0 有的時...
    蛇發(fā)女妖閱讀 2,843評論 9 24
  • 一.什么是單例模式 單例模式的定義:確保一個類只有一個實例用踩,并提供一個訪問他的全局訪問點渠退。單例模式是幾個設(shè)計模式中...
    Geeks_Liu閱讀 2,225評論 0 10
  • 單例模式,顧名思義脐彩,指的是一個類只存在一個實例碎乃。 那么,如何保證某一個類只存在一個實例呢惠奸?對象的創(chuàng)建是通過類的構(gòu)造...
    哇哇哇one閱讀 311評論 0 2
  • 親愛的兜兒梅誓, 又到周五,謝謝你的翻轉(zhuǎn)課堂佛南,才成全了我們集書法攝影及心理按摩于一體的美好時光梗掰。我們的認真可以在凝神聚...
    蔡新花閱讀 88評論 0 0