單例模式精講

設(shè)計模式

一哈踱、設(shè)計模式的的6大原則

總原則-開閉原則

對擴展開放,對修改封閉慢显。在程序需要進行拓展的時候爪模,不能去修改原有的代碼,而是要擴展原有代碼荚藻,實現(xiàn)一個熱插拔的效果屋灌。所以一句話概括就是:為了使程序的擴展性好,易于維護和升級鞋喇。

想要達到這樣的效果声滥,我們需要使用接口和抽象類等,后面的具體設(shè)計中我們會提到這點侦香。

(1)單一職責(zé)原則

一個類最好只有一個功能落塑,不要一個類有2個功能,除非邏輯很簡單

(2)接口隔離原則

每個接口中不存在子類用不到卻必須實現(xiàn)的方法罐韩,如果不然憾赁,就要將接口拆分。使用多個隔離的接口散吵,比使用單個接口(多個接口方法集合到一個的接口)要好龙考。

(3)依賴倒置原則

面向接口編程,依賴于抽象而不依賴于具體矾睦。寫代碼時用到具體類時晦款,不與具體類交互,而與具體類的上層接口交互枚冗。

(4)里氏替換原則

任何基類可以出現(xiàn)的地方缓溅,子類一定可以出現(xiàn)。里氏替換原則是繼承復(fù)用的基石赁温,只有當衍生類可以替換基類坛怪,軟件單位的功能不受到影響時,基類才能真正被復(fù)用股囊,而衍生類也能夠在基類的基礎(chǔ)上增加新的行為袜匿。

里氏代換原則是對“開-閉”原則的補充。實現(xiàn)“開閉”原則的關(guān)鍵步驟就是抽象化稚疹。而基類與子類的繼承關(guān)系就是抽象化的具體實現(xiàn)居灯,所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。里氏替換原則中,子類對父類的方法盡量不要重寫和重載穆壕。因為父類代表了定義好的結(jié)構(gòu)待牵,通過這個規(guī)范的接口與外界交互,子類不應(yīng)該隨便破壞它喇勋。

當子類繼承父類時,最好不要修改父類的方法偎行,繼承意味著耦合川背,可以通過組合、聚合解耦

(5)迪米特法則 (最少知道原則)

一個類對自己依賴的類知道的越少越好蛤袒。無論被依賴的類多么復(fù)雜熄云,都應(yīng)該將邏輯封裝在方法的內(nèi)部,通過public方法提供給外部妙真。這樣當被依賴的類變化時缴允,才能最小的影響該類。

最少知道原則的另一個表達方式是:只與直接的朋友通信珍德。類之間只要有耦合關(guān)系练般,就叫朋友關(guān)系。耦合分為依賴锈候、關(guān)聯(lián)薄料、聚合、組合等泵琳。我們稱出現(xiàn)為成員變量摄职、方法參數(shù)、方法返回值中的類為直接朋友获列。局部變量谷市、臨時變量則不是直接的朋友。我們要求陌生的類不要作為局部變量出現(xiàn)在類中击孩。

(6)合成復(fù)用原則

盡量使用聚合和組合代替繼承

二迫悠、設(shè)計模式的分類

設(shè)計模式分為三大類:

創(chuàng)建型模式,共五種:

單例模式溯壶、工廠方法模式及皂、抽象工廠模式、建造者模式且改、原型模式

結(jié)構(gòu)型模式验烧,共七種:

適配器模式、裝飾者模式又跛、代理模式碍拆、外觀模式、橋接模式、組合模式感混、享元模式端幼。

行為型模式弧满,共十一種:

策略模式婆跑、模板方法模式、觀察者模式庭呜、迭代子模式滑进、責(zé)任鏈模式、命令模式募谎、備忘錄模式扶关、狀態(tài)模式、訪問者模式数冬、中介者模式节槐、解釋器模式。

PS:其實還有兩類:并發(fā)型模式和線程池模式(多線程相關(guān))拐纱。

學(xué)習(xí)建議:

設(shè)計模式需要幾個階段的學(xué)習(xí)铜异,沒有大量項目經(jīng)驗的時候?qū)W習(xí),可能只是了解戳玫,當有了一些項目場景的時候熙掺,才會深刻體會到其中的奧妙。

后面我會一個一個詳細講解每種設(shè)計模式咕宿,每講解一種會把它設(shè)置成鏈接币绩,請收藏

三、設(shè)計模式精講

一府阀、單例模式

單例模式有以下特點:

  1. 單例類只能有一個實例缆镣。
  2. 單例類必須自己創(chuàng)建自己的唯一實例。
  3. 單例類必須給所有其他對象提供這一實例试浙。

單例模式確保某個類只有一個實例董瞻,而且自行實例化并向整個系統(tǒng)提供這個實例。在計算機系統(tǒng)中田巴,線程池钠糊、緩存、日志對象壹哺、對話框抄伍、打印機、顯卡的驅(qū)動程序?qū)ο蟪1辉O(shè)計成單例管宵。這些應(yīng)用都或多或少具有資源管理器的功能截珍。每臺計算機可以有若干個打印機攀甚,但只能有一個Printer Spooler,以避免兩個打印作業(yè)同時輸出到打印機中岗喉。每臺計算機可以有若干通信端口秋度,系統(tǒng)應(yīng)當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用钱床〖运梗總之,選擇單例模式就是為了避免不一致狀態(tài)诞丽,避免政出多頭

話不多說鲸拥,先上一個流程圖看看單例模式的進化史:

單例模式.png

一、餓漢式單例

public class HungrySingleton {

    public static void main(String[] args) {
        //測試
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
    }
}

//餓漢式

class Singleton {
    
    //1. 構(gòu)造器私有化, 外部不能new
    private Singleton() {
        
    }
    
    //2.一開始創(chuàng)建對象實例
    private final static Singleton instance = new Singleton();
    
    //3. 提供一個公有的靜態(tài)方法僧免,返回實例對象
    public static Singleton getInstance() {
        return instance;
    }
    
}

ps:Singleton通過將構(gòu)造方法限定為private避免了類在外部被實例化,在同一個虛擬機范圍內(nèi)捏浊,Singleton的唯一實例只能通過getInstance()方法訪問懂衩。(事實上,通過Java反射機制是能夠?qū)嵗瘶?gòu)造方法為private的類的金踪,那基本上會使所有的Java單例實現(xiàn)失效浊洞。此問題在此處不做討論,姑且掩耳盜鈴地認為反射機制不存在胡岔。)

優(yōu)點: 餓漢式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用法希,以后不再改變,所以天生是線程安全的

缺點:優(yōu)點正是其缺點靶瘸,由于一開始就加載苫亦,如果沒有使用就會白白占用內(nèi)存,于是有了下面的懶漢式
二怨咪、線程不安全懶漢式

public class NotSafeLazySingleton {

    public static void main(String[] args) {
        System.out.println("懶漢式1 屋剑, 線程不安全~");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
    }
}

class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    //提供一個靜態(tài)的公有方法,當使用到該方法時诗眨,才去創(chuàng)建 instance唉匾,即懶漢式
    //由于沒有加鎖,大量線程同時訪問可能出現(xiàn)實例化多個對象匠楚,所以線程不安全
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

當大量線程同時訪問getInstance()時巍膘,就可能創(chuàng)建多個實例,所以不可取

三芋簿、線程安全同步方法(鎖方法鎖粒度較大)

public class SynchronizedMethodSingleton {

    public static void main(String[] args) {
        System.out.println("懶漢式2 峡懈, 線程安全~");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
    }
}

// 懶漢式(線程安全,同步方法)
class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    //提供一個靜態(tài)的公有方法益咬,加入同步處理的代碼逮诲,解決線程安全問題
    //即懶漢式
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

由于鎖加在了方法上帜平,高并發(fā)下會降低性能,所以應(yīng)該減小鎖的粒度

四梅鹦、線程安全同步代碼塊(雙重檢驗鎖降低了鎖粒度裆甩,推薦使用)

public class SynchronizedBlockSingleton {

    public static void main(String[] args) {
        System.out.println("雙重檢查");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
    }

}

// 懶漢式(線程安全,同步代碼塊)
class Singleton {
    //這里加volatile關(guān)鍵字禁止指令重排
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    //提供一個靜態(tài)的公有方法齐唆,加入雙重檢查代碼嗤栓,解決線程安全問題, 同時解決懶加載問題
    //同時保證了效率, 推薦使用
    
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
            
        }
        return instance;
    }
}

ps:這里instance之所以要用volatile修飾,是為了禁止指令重排箍邮,防止后面線程訪問到前面線程未初始化的對象茉帅,詳情參考以下鏈接:

https://blog.csdn.net/OrangeRawNorthland/article/details/83788412

雙重檢驗鎖盡管降低了鎖粒度,但畢竟還是有鎖锭弊,有鎖就會降低并發(fā)性能堪澎,那么可不可以不加鎖呢?答案是肯定的味滞,那就是使用靜態(tài)內(nèi)部類樱蛤,靜態(tài)內(nèi)部類不會在一開始就加載,會在第一次使用時才加載剑鞍,所以它既解決了餓漢式可能白白占用資源的問題昨凡,又解決了懶漢式為解決線程安全問題加鎖導(dǎo)致的性能問題,可謂是一舉兩得

五蚁署、使用靜態(tài)內(nèi)部類(推薦使用)

public class StaticInnerClassSingleton {

    public static void main(String[] args) {
        System.out.println("使用靜態(tài)內(nèi)部類完成單例模式");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
    }

}

// 靜態(tài)內(nèi)部類完成便脊, 推薦使用
class Singleton {
    private static volatile Singleton instance;
    
    //構(gòu)造器私有化
    private Singleton() {}
    
    //寫一個靜態(tài)內(nèi)部類,該類中有一個靜態(tài)屬性 Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton(); 
    }
    
    //提供一個靜態(tài)的公有方法,直接返回SingletonInstance.INSTANCE
    
    public static synchronized Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

六光戈、枚舉實現(xiàn)單例

public class EnumSingleton {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance == instance2);//true
        instance.sayOK();
    }
}

//使用枚舉哪痰,可以實現(xiàn)單例, 推薦
enum Singleton {
    INSTANCE; //屬性
    public void sayOK() {
        System.out.println("ok~");
    }
}

枚舉類實現(xiàn)單例模式是 effective java 作者極力推薦的單例實現(xiàn)模式,因為枚舉類型是線程安全的田度,并且只會裝載一次妒御,設(shè)計者充分的利用了枚舉的這個特性來實現(xiàn)單例模式,枚舉的寫法非常簡單镇饺,而且枚舉類型是所用單例實現(xiàn)中唯一一種不會被破壞的單例實現(xiàn)模式乎莉。( 其他方法都會通過反射的方式破壞單例 )

單例模式總結(jié):

單例模式為一個面向?qū)ο蟮膽?yīng)用程序提供了對象唯一的訪問點,不管它實現(xiàn)何種功能奸笤,整個應(yīng)用程序都會同享一個實例對象 惋啃。

對于單例模式的幾種實現(xiàn)方式,知道餓漢式和懶漢式的區(qū)別监右,線程安全边灭,資源加載的時機,還有懶漢式為了實現(xiàn)線程安全的幾種方式的細微差別健盒。

原創(chuàng)不易绒瘦,覺得寫得不錯的話就點點贊關(guān)注關(guān)注唄称簿,我的微信公眾號:java時光

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黎棠,一起剝皮案震驚了整個濱河市力麸,隨后出現(xiàn)的幾起案子乃正,更是在濱河造成了極大的恐慌瘦癌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栅螟,死亡現(xiàn)場離奇詭異幅疼,居然都是意外死亡凹嘲,警方通過查閱死者的電腦和手機呜魄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門悔叽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爵嗅,你說我怎么就攤上這事娇澎。” “怎么了睹晒?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵九火,是天一觀的道長。 經(jīng)常有香客問我册招,道長,這世上最難降的妖魔是什么勒极? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任是掰,我火速辦了婚禮,結(jié)果婚禮上辱匿,老公的妹妹穿的比我還像新娘键痛。我一直安慰自己,他們只是感情好匾七,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布絮短。 她就那樣靜靜地躺著,像睡著了一般昨忆。 火紅的嫁衣襯著肌膚如雪丁频。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天邑贴,我揣著相機與錄音席里,去河邊找鬼。 笑死拢驾,一個胖子當著我的面吹牛奖磁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播繁疤,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼咖为,長吁一口氣:“原來是場噩夢啊……” “哼秕狰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躁染,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鸣哀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后褐啡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诺舔,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年备畦,在試婚紗的時候發(fā)現(xiàn)自己被綠了低飒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡懂盐,死狀恐怖褥赊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莉恼,我是刑警寧澤拌喉,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站俐银,受9級特大地震影響尿背,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捶惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一田藐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吱七,春花似錦汽久、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吝岭,卻和暖如春三痰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苍碟。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工酒觅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人微峰。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓舷丹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜓肆。 傳聞我的和親對象是個殘疾皇子颜凯,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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