關(guān)于java單例模式伍玖,這篇已經(jīng)講得很清楚了嫩痰,建議收藏!

概念

java中單例模式是一種常見的設(shè)計(jì)模式窍箍,單例模式分三種:懶漢式單例串纺、餓漢式單例丽旅、登記式單例三
種。

特點(diǎn)

單例模式有以下特點(diǎn):

  • 單例類只能有一個(gè)實(shí)例纺棺。
  • 單例類必須自己創(chuàng)建自己的唯一實(shí)例榄笙。
  • 單例類必須給所有其他對(duì)象提供這一實(shí)例
    單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例祷蝌。避免生成多個(gè)對(duì)
    象保證只對(duì)這一個(gè)唯一對(duì)象進(jìn)行操作茅撞,保證線程的安全和數(shù)據(jù)的安全.

餓漢式:

顧名思義,餓漢式就是在第一次引用該類的時(shí)候就創(chuàng)建對(duì)象實(shí)例巨朦,而不管實(shí)際是否需要?jiǎng)?chuàng)建米丘。代碼如
下:
下面看一個(gè)示例:

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

這樣做的好處是編寫簡單,但是無法做到延遲創(chuàng)建對(duì)象罪郊。但是我們很多時(shí)候都希望對(duì)象可以盡可能地
延遲加載蠕蚜,從而減小負(fù)載尚洽,所以就需要下面的懶漢式

懶漢式:

單線程寫法
public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; }}
這種寫法是最簡單的悔橄,由私有構(gòu)造器和一個(gè)公有靜態(tài)工廠方法構(gòu)成,在工廠方法中對(duì)singleton進(jìn)行null
判斷腺毫,如果是null就new一個(gè)出來癣疟,最后返回singleton對(duì)象。這種方法可以實(shí)現(xiàn)延時(shí)加載潮酒,但是有一個(gè)
致命弱點(diǎn):線程不安全睛挚。如果有兩條線程同時(shí)調(diào)用getSingleton()方法,就有很大可能導(dǎo)致重復(fù)創(chuàng)建對(duì)
象急黎。

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

考慮線程安全的寫法:

這種寫法考慮了線程安全扎狱,將對(duì)singleton的null判斷以及new的部分使用synchronized進(jìn)行加鎖。同
時(shí)勃教,對(duì)singleton對(duì)象使用volatile關(guān)鍵字進(jìn)行限制淤击,保證其對(duì)所有線程的可見性,并且禁止對(duì)其進(jìn)行指
令重排序優(yōu)化故源。如此即可從語義上保證這種單例模式寫法是線程安全的污抬。注意,這里說的是語義上绳军,實(shí)
際使用中還是存在小坑的

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

兼顧線程安全和效率的寫法:

雖然上面這種寫法是可以正確運(yùn)行的印机,但是其效率低下,還是無法實(shí)際應(yīng)用门驾。因?yàn)槊看握{(diào)用
getSingleton()方法射赛,都必須在synchronized這里進(jìn)行排隊(duì),而真正遇到需要new的情況是非常少的奶是。
所以楣责,就誕生了第三種寫法

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

這種寫法被稱為“雙重檢查鎖”顷蟆,顧名思義,就是在getSingleton()方法中腐魂,進(jìn)行兩次null檢查帐偎。看似多
此一舉蛔屹,但實(shí)際上卻極大提升了并發(fā)度削樊,進(jìn)而提升了性能。為什么可以提高并發(fā)度呢兔毒?就像上文說的漫贞,
在單例中new的情況非常少,絕大多數(shù)都是可以并行的讀操作育叁。因此在加鎖前多進(jìn)行一次null檢查就可
以減少絕大多數(shù)的加鎖操作迅脐,執(zhí)行效率提高的目的也就達(dá)到了

小坑:

那么,這種寫法是不是絕對(duì)安全呢豪嗽?前面說了谴蔑,從語義角度來看,并沒有什么問題龟梦。但是其實(shí)還是有
坑隐锭。說這個(gè)坑之前我們要先來看看 volatile 這個(gè)關(guān)鍵字。其實(shí)這個(gè)關(guān)鍵字有兩層語義计贰。第一層語義相信
大家都比較熟悉钦睡,就是可見性≡甑梗可見性指的是在一個(gè)線程中對(duì)該變量的修改會(huì)馬上由工作內(nèi)存( Work
Memory )寫回主內(nèi)存( Main Memory )荞怒,所以會(huì)馬上反應(yīng)在其它線程的讀取操作中。順便一提秧秉,工
作內(nèi)存和主內(nèi)存可以近似理解為實(shí)際電腦中的高速緩存和主存褐桌,工作內(nèi)存是線程獨(dú)享的,主存是線程共
享的福贞。 volatile 的第二層語義是禁止指令重排序優(yōu)化撩嚼。大家知道我們寫的代碼(尤其是多線程代碼),
由于編譯器優(yōu)化挖帘,在實(shí)際執(zhí)行的時(shí)候可能與我們編寫的順序不同完丽。編譯器只保證程序執(zhí)行結(jié)果與源代碼
相同,卻不保證實(shí)際指令的順序與源代碼相同拇舀。這在單線程看起來沒什么問題逻族,然而一旦引入多線程,
這種亂序就可能導(dǎo)致嚴(yán)重問題骄崩。 volatile 關(guān)鍵字就可以從語義上解決這個(gè)問題聘鳞。
注意薄辅,前面反復(fù)提到 “ 從語義上講是沒有問題的 ” ,但是很不幸抠璃,禁止指令重排優(yōu)化這條語義直到 jdk1.5
以后才能正確工作站楚。此前的 JDK 中即使將變量聲明為 volatile 也無法完全避免重排序所導(dǎo)致的問題。所
以搏嗡,在 jdk1.5 版本前窿春,雙重檢查鎖形式的單例模式是無法保證線程安全的。

靜態(tài)內(nèi)部類法:

那么采盒,有沒有一種延時(shí)加載旧乞,并且能保證線程安全的簡單寫法呢?我們可以把 Singleton 實(shí)例放到一個(gè)
靜態(tài)內(nèi)部類中磅氨,這樣就避免了靜態(tài)實(shí)例在 Singleton 類加載的時(shí)候就創(chuàng)建對(duì)象尺栖,并且由于靜態(tài)內(nèi)部類只
會(huì)被加載一次,所以這種寫法也是線程安全的:

public class Singleton { 
    private static class Holder { 
        private static Singleton singleton = new Singleton(); 
    }
    private Singleton(){} 
    public static Singleton getSingleton(){ 
        return Holder.singleton; 
    }
}

但是烦租,上面提到的所有實(shí)現(xiàn)方式都有兩個(gè)共同的缺點(diǎn):
都需要額外的工作(Serializable延赌、transient、readResolve())來實(shí)現(xiàn)序列化左权,否則每次反序列化一 個(gè)序列化的對(duì)象實(shí)例時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例皮胡。
可能會(huì)有人使用反射強(qiáng)行調(diào)用我們的私有構(gòu)造器(如果要避免這種情況,可以修改構(gòu)造器赏迟,讓它在 創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋異常)。

登記式:

登記式單例實(shí)際上維護(hù)了一組單例類的實(shí)例蠢棱,將這些實(shí)例存放在一個(gè) Map (登記毙可薄)中,對(duì)于已經(jīng)登記
過的實(shí)例泻仙,則從 Map 直接返回糕再,對(duì)于沒有登記的,則先登記玉转,然后返回突想。

public class Singleton { 
    private static Map<String, Singleton> map = new HashMap<String, Singleton> (); 
    static { 
        Singleton single = new Singleton(); 
        map.put(single.getClass().getName(), single); 
    }
    // 保護(hù)的默認(rèn)構(gòu)造子 protected Singleton() { }
    // 靜態(tài)工廠方法,返還此類惟一的實(shí)例 
    public static Singleton getInstance(String name) { 
        if (name == null) { 
            name = Singleton.class.getName(); 
            System.out.println("name == null" + "--->name=" + name); 
        }
        if (map.get(name) == null) { 
            try {
                map.put(name, (Singleton) Class.forName(name).newInstance());
            } catch (InstantiationException e) { 
                e.printStackTrace(); 
            } catch (IllegalAccessException e) { 
                e.printStackTrace(); 
            } catch (ClassNotFoundException e) { 
                e.printStackTrace(); 
            } 
        }
        return map.get(name);
    }
}

它用的比較少,另外其實(shí)內(nèi)部實(shí)現(xiàn)還是用的餓漢式單例究抓,因?yàn)槠渲械?static 方法塊猾担,它的單例在類被裝
載的時(shí)候就被實(shí)例化了。

枚舉寫法:

當(dāng)然刺下,還有一種更加優(yōu)雅的方法來實(shí)現(xiàn)單例模式绑嘹,那就是枚舉寫法

public enum Singleton { 
    INSTANCE; 
    private String name; 
    public String getName(){ return name; }
    public void setName(String name){ this.name = name; }
}

使用枚舉除了線程安全和防止反射強(qiáng)行調(diào)用構(gòu)造器之外,還提供了自動(dòng)序列化機(jī)制橘茉,防止反序列化
的時(shí)候創(chuàng)建新的對(duì)象工腋。因此推薦盡可能地使用枚舉來實(shí)現(xiàn)單例姨丈。
最后,不管采取何種方案擅腰,請(qǐng)時(shí)刻牢記單例的三大要點(diǎn):
線程安全
延遲加載
序列化與反序列化安全

最后

在文章的最后作者為大家整理了很多資料蟋恬!包括java核心知識(shí)點(diǎn)+全套架構(gòu)師學(xué)習(xí)資料和視頻+一線大廠面試寶典+面試簡歷模板+阿里美團(tuán)網(wǎng)易騰訊小米愛奇藝快手嗶哩嗶哩面試題+Spring源碼合集+Java架構(gòu)實(shí)戰(zhàn)電子書等等!

全部免費(fèi)分享給大家趁冈,有需要的朋友關(guān)注公眾號(hào):【前程有光】自冉钕帧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末箱歧,一起剝皮案震驚了整個(gè)濱河市矾飞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呀邢,老刑警劉巖洒沦,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異价淌,居然都是意外死亡申眼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門蝉衣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來括尸,“玉大人,你說我怎么就攤上這事病毡”舴” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵啦膜,是天一觀的道長有送。 經(jīng)常有香客問我,道長僧家,這世上最難降的妖魔是什么雀摘? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮八拱,結(jié)果婚禮上阵赠,老公的妹妹穿的比我還像新娘。我一直安慰自己肌稻,他們只是感情好清蚀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灯萍,像睡著了一般轧铁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旦棉,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天齿风,我揣著相機(jī)與錄音药薯,去河邊找鬼。 笑死救斑,一個(gè)胖子當(dāng)著我的面吹牛童本,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脸候,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼穷娱,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了运沦?” 一聲冷哼從身側(cè)響起泵额,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎携添,沒想到半個(gè)月后嫁盲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烈掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年羞秤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片左敌。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘾蛋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矫限,到底是詐尸還是另有隱情哺哼,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布奇唤,位于F島的核電站幸斥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咬扇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一廊勃、第九天 我趴在偏房一處隱蔽的房頂上張望懈贺。 院中可真熱鬧逊抡,春花似錦尸折、人聲如沸板惑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸟缕。三九已至搀庶,卻和暖如春按脚,著一層夾襖步出監(jiān)牢的瞬間溉卓,已是汗流浹背皮迟。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工搬泥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伏尼。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓忿檩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爆阶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子燥透,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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