單例模式詳解

單例模式

所謂單例設(shè)計(jì)模式,就是采用一定的方法宇葱,保證在整個(gè)的軟件系統(tǒng)中,對(duì)某個(gè)類只能存在一個(gè)對(duì)象實(shí)例刊头,并且該類只提供一個(gè)取得其對(duì)象實(shí)例的方法(靜態(tài)方法)黍瞧。

1、具體實(shí)現(xiàn)步驟

  • 構(gòu)造器私有化(防止外部new這個(gè)實(shí)例)
  • 類的內(nèi)部創(chuàng)建對(duì)象
  • 向外暴露一個(gè)靜態(tài)的公共方法
  • 代碼實(shí)現(xiàn)

2原杂、單例模式注意事項(xiàng)和細(xì)節(jié)說(shuō)明

  • 單例模式保證了系統(tǒng)內(nèi)存中該類只存在一個(gè)對(duì)象印颤,節(jié)省了系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建銷毀的對(duì)象穿肄,使用單例模式可以提高系統(tǒng)性能年局。
  • 當(dāng)想實(shí)例化一一個(gè)單例類的時(shí)候,必須要記住使用相應(yīng)的獲取對(duì)象的方法咸产,而不是使用new矢否。
  • 單例模式使用的場(chǎng)景:需要頻繁的進(jìn)行創(chuàng)建和銷毀的對(duì)象、創(chuàng)建對(duì)象時(shí)耗時(shí)過(guò)多或耗費(fèi)資源過(guò)多(即:重量級(jí)對(duì)象)脑溢,但又經(jīng)常用到的對(duì)象僵朗、工具類對(duì)象、頻繁訪問(wèn)數(shù)據(jù)庫(kù)或文件的對(duì)象(比如數(shù)據(jù)源屑彻、session工廠 等)

3验庙、單例模式方案合集

3.1 餓漢式(靜態(tài)常量)

3.1.1 餓漢式(靜態(tài)常量)代碼實(shí)現(xiàn)
class person{
    //構(gòu)造器私有化化,外部不能new    
    private person(){}
    //本類內(nèi)部創(chuàng)建對(duì)象實(shí)例    
    private final static person onePerson = new person();
    //提供一個(gè)公有的靜態(tài)方法社牲,返回實(shí)例對(duì)象    
    public static person getPerson(){        
        return person;    
    }
}
3.1.2 優(yōu)缺點(diǎn)
  • 這種寫法比較簡(jiǎn)單粪薛,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問(wèn)題搏恤。
  • 在類裝載的時(shí)候就完成實(shí)例化违寿,沒(méi)有達(dá)到Lazy Loading(懶加載)的效果,如果從始至終未用過(guò)這個(gè)實(shí)例熟空,則會(huì)造成內(nèi)存的浪費(fèi)藤巢。
  • 這種方式基于類加載機(jī)制避免了多線程的問(wèn)題,沒(méi)有達(dá)到Lazy Loading(懶加載)的效果痛阻【瘢可用腮敌,但有可能造成內(nèi)存的浪費(fèi)阱当。

3.2 懶漢式(靜態(tài)代碼塊)

3.2.1 懶漢式(靜態(tài)代碼塊)代碼實(shí)現(xiàn)
class person{        
    //構(gòu)造器私有化化俏扩,外部不能new    
    private person(){}
    //本類內(nèi)部創(chuàng)建對(duì)象實(shí)例    
    private static person onePerson;        
    //在靜態(tài)代碼塊中實(shí)例化這個(gè)對(duì)象    
    static{        
        onePerson = new person();    
    }
    //提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象    
    public static person getPerson(){        
        return person;    
    }     
}
3.2.2 優(yōu)缺點(diǎn)

優(yōu)缺點(diǎn)同上(餓漢式靜態(tài)常量)弊添;

3.3 懶漢式(線程不安全)

3.3.1 懶漢式(線程不安全)代碼實(shí)現(xiàn)
class person{        
    //本類內(nèi)部創(chuàng)建對(duì)象實(shí)例    
    private static person onePerson;        
    //構(gòu)造器私有化化录淡,外部不能new    
    private person(){}     
    //提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象    
    public static person getPerson(){        
        if(onePerson == null){            
            onePerson = new person();        
        }        
        return person;    
    }     
}
3.3.2 優(yōu)缺點(diǎn)
  • 起到了Lazy Loading(懶加載)的效果油坝,但是只能在單線程下使用嫉戚。
  • 如果在多線程下,一個(gè)線程進(jìn)入了if (onePerson == null) 判斷語(yǔ)句塊澈圈,還未來(lái)得及往下執(zhí)行彬檀,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例瞬女。所以在多線程環(huán)境下不可使用這種方式
  • 結(jié)論:在實(shí)際開(kāi)發(fā)中窍帝,不要使用這種方式。

3.4 懶漢式(線程安全)

3.4.1 懶漢式(線程安全)代碼實(shí)現(xiàn)
class person{        
    //本類內(nèi)部創(chuàng)建靜態(tài)對(duì)象實(shí)例    
    private static person onePerson;        
    //構(gòu)造器私有化化诽偷,外部不能new    
    private person(){}     
    //提供一個(gè)公有的靜態(tài)方法坤学,返回實(shí)例對(duì)象    
    //加入同步處理的代碼,解決線程安全的問(wèn)題    
    public static synchronized person getPerson(){        
        if(onePerson == null){            
            onePerson = new person();        
        }        return person;    
    }     
}
3.4.2 優(yōu)缺點(diǎn)
  • 解決了線程不安全問(wèn)題
  • 效率太低了报慕,每個(gè)線程在想獲得類的實(shí)例時(shí)候深浮,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步。而其實(shí)這個(gè)方法只執(zhí)行一次實(shí)例化代碼就夠了眠冈,后面的想獲得該類實(shí)例飞苇,直接return就行了。方法進(jìn)行同步效率太低
  • 結(jié)論:在實(shí)際開(kāi)發(fā)中蜗顽,不推薦使用這種方式

3.5 雙重檢查單例模式

3.5.1 雙重檢查單例模式代碼實(shí)現(xiàn)
class person{    
    //本類內(nèi)部創(chuàng)建靜態(tài)對(duì)象實(shí)例    
    private static volatile person onePerson;        
    //私有化構(gòu)造方法玄柠,外部不能new    
    private person() {}        
    //提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
    
    public static person getPerson() {        
        //第一次檢查诫舅,判斷是否已經(jīng)實(shí)例化        
        if (onePerson == null) {            
            //如果沒(méi)有實(shí)例化羽利,則為這個(gè)對(duì)象實(shí)例化            
            synchronized (person.class) {                
                //第二次檢查,判斷是否被其他線程給實(shí)例化了                
                if (onePerson == null) {                    
                    //如果沒(méi)有被實(shí)例化刊懈,說(shuō)明自己是第一個(gè)進(jìn)入此方法的線程这弧,并給其實(shí)例化。
                    onePerson = new person();
                }
            }
        }
        //返回實(shí)例對(duì)象
        return onePerson;
    } 
}
3.5.2 優(yōu)缺點(diǎn)
  • Double-Check概念 是多線程開(kāi)發(fā)中常使用到的虚汛,如代碼中所示匾浪,我們進(jìn)行了兩次 if (singleton == null)檢查,這樣就可以保證線程安全了卷哩。
  • 這樣蛋辈,實(shí)例化代碼只用執(zhí)行一次,后面再次訪問(wèn)時(shí),判斷if (singleton == null)冷溶,直接return實(shí)例化對(duì)象渐白,也避免的反復(fù)進(jìn)行方法同步。
  • 線程安全逞频;延遲加載纯衍;效率較高;
  • 結(jié)論:在實(shí)際開(kāi)發(fā)中苗胀,推薦使用這種單例設(shè)計(jì)模式襟诸。

3.6 靜態(tài)內(nèi)部類單例模式

3.6.1 靜態(tài)內(nèi)部類單例模式代碼實(shí)現(xiàn)
class person{
    //構(gòu)造方法私有化
    private person(){}        
    //靜態(tài)內(nèi)部類,其中有著靜態(tài)屬性
    private static class personInstance {        
        private static final person onePersonInstance = new person();    
    }
    //靜態(tài)公有方法    
    public static person getPerson() {        
        return personInstance.onePersonInstance;    
    } 
}
3.6.2 優(yōu)缺點(diǎn)
  • 這種方式采用了類裝載的機(jī)制來(lái)保證初始化實(shí)例時(shí)只有一個(gè)線程基协。
  • 靜態(tài)內(nèi)部類方式在 person 類被裝載時(shí)并不會(huì)立即實(shí)例化歌亲,而是在需要實(shí)例化時(shí),調(diào)用getPerson() 方法澜驮, 才會(huì)裝載 personInstance 類应结,從而完成 onePersonInstance 的實(shí)例化。
  • 類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化泉唁,所以在這里鹅龄,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時(shí)亭畜,別的線程是無(wú)法進(jìn)入的扮休。
  • 優(yōu)點(diǎn):避免了線程不安全,利用靜態(tài)內(nèi)部類特點(diǎn)實(shí)現(xiàn)延遲加載拴鸵,效率高玷坠。
  • 結(jié)論:推薦使用。

3.7 枚舉單例模式

3.7.1 枚舉單例模式代碼實(shí)現(xiàn)
enum person {    
    //屬性    
    onePerson;
    //方法
    public void sayHello() {        
        System.out.println("Hello!");    
    } 
} 

public class PersonTest {
    public static void main(String[] args) {
        person personOne = person.onePerson;
        person personTwo = person.onePerson;
        System.out.println(personOne == personTwo);
        System.out.println(personOne.hashCode());
        System.out.println(personTwo.hashCode());
        personOne.sayHello();
    } 
}
3.7.2 優(yōu)缺點(diǎn)
  • 這借助JDK1.5中添加的枚舉來(lái)實(shí)現(xiàn)單例模式劲藐。不僅能避免多線程同步問(wèn)題八堡,而且還能防止反序列化重新創(chuàng)建新的對(duì)象。
  • 這種方式是Effective Java作者Josh Bloch提倡的方式聘芜。
  • 結(jié)論:推薦使用兄渺。

3.8 反射創(chuàng)建單例模式(新補(bǔ)充)

仔細(xì)思考下,我們是不是可以采用工廠方法模式實(shí)現(xiàn)單例模式的功能呢汰现?單例模式的核心要求就是在內(nèi)存中只有一個(gè)對(duì)象挂谍,通過(guò)工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個(gè)對(duì)象,代碼如下所示瞎饲。

//單例類
public class Singleton {
    //不允許通過(guò)new產(chǎn)生一個(gè)對(duì)象
    private Singleton() {}
    public void doSomething() {
        //業(yè)務(wù)處理
    }
}

Singleton保證不能通過(guò)正常的渠道建立一個(gè)對(duì)象口叙,那 SingletonFactory 如何建立一個(gè)單例對(duì)象呢?答案是通過(guò)反射方式創(chuàng)建嗅战,如代碼清單下所示妄田。

//負(fù)責(zé)生成單例的工廠類
pub1ic class SingletonFactory {
    private static Singleton singleton;
    static{
        try {
            Class cl = Class.forName (Singleton.class.getName());
            //獲得無(wú)參構(gòu)造
            Constructor constructor = cl.getDeclaredConstructor();
            //設(shè)置無(wú)參構(gòu)造是可訪問(wèn)的
            constructor.setAccessible(true);
            //產(chǎn)生一個(gè)實(shí)例對(duì)象
            singleton = (Sing1eton) constructor.newInstance();
        } catch (Exception e) {
            //異常處理
        }
    }
    public static Singleton getSingleton() {
        return singleton;
    }
}

通過(guò)獲得類構(gòu)造器,然后設(shè)置訪問(wèn)權(quán)限,生成一個(gè)對(duì)象疟呐,然后提供外部訪問(wèn)脚曾,保證內(nèi)存中的對(duì)象唯一。當(dāng)然萨醒,其他類也可以通過(guò)反射的方式建立一個(gè)單例對(duì)象斟珊,確實(shí)如此苇倡,但是一個(gè)項(xiàng)目或團(tuán)隊(duì)是有章程和規(guī)范的富纸,何況已經(jīng)提供了一個(gè)獲得單例對(duì)象的方法,為什么還要重新創(chuàng)建一個(gè)新對(duì)象呢旨椒?除非是有人作惡晓褪。
以上通過(guò)工廠方法模式創(chuàng)建了一個(gè)單例對(duì)象,該框架可以繼續(xù)擴(kuò)展综慎,在一個(gè)項(xiàng)目中可以產(chǎn)生一個(gè)單例構(gòu)造器涣仿,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private),然后通過(guò)擴(kuò)展該框架示惊,只要輸入一個(gè)類型就可以獲得唯一的一個(gè)實(shí)例好港。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市米罚,隨后出現(xiàn)的幾起案子钧汹,更是在濱河造成了極大的恐慌,老刑警劉巖录择,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拔莱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡隘竭,警方通過(guò)查閱死者的電腦和手機(jī)塘秦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)动看,“玉大人尊剔,你說(shuō)我怎么就攤上這事×饨裕” “怎么了赋兵?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)搔预。 經(jīng)常有香客問(wèn)我霹期,道長(zhǎng),這世上最難降的妖魔是什么拯田? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任历造,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吭产。我一直安慰自己侣监,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布臣淤。 她就那樣靜靜地躺著橄霉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邑蒋。 梳的紋絲不亂的頭發(fā)上姓蜂,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音医吊,去河邊找鬼钱慢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卿堂,可吹牛的內(nèi)容都是我干的束莫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼草描,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼览绿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起穗慕,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饿敲,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后揍诽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體诀蓉,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年暑脆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了渠啤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡添吗,死狀恐怖沥曹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碟联,我是刑警寧澤妓美,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站鲤孵,受9級(jí)特大地震影響壶栋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜普监,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一贵试、第九天 我趴在偏房一處隱蔽的房頂上張望琉兜。 院中可真熱鬧,春花似錦毙玻、人聲如沸豌蟋。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梧疲。三九已至,卻和暖如春运准,著一層夾襖步出監(jiān)牢的瞬間幌氮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工戳吝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浩销,地道東北人贯涎。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓听哭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親塘雳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陆盘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355