你給我解釋解釋拙泽?什么叫單例模式?

你給我解釋解釋裸燎?什么叫Tm單例模式顾瞻?

餓漢式

顧名思義:在類加載時就進(jìn)行單例對象的創(chuàng)建,十分簡單德绿。

代碼如下:

public class Hungry {
    // 餓漢式單例

    private Hungry(){
        // 模擬類中屬性
        byte[] data1 = new byte[1024];
        byte[] data2 = new byte[1024];
        byte[] data3 = new byte[1024];
    }
    private static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }
}

存在的問題是顯而易見的:如果類始終不需要使用荷荤,單例對象還是會一直占用內(nèi)存資源

懶漢式

僅在需求單例對象時才進(jìn)行創(chuàng)建,把對象的創(chuàng)建時機(jī)延后了移稳。

public class LazyMan {
    // 懶漢式
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+" is running constructor");
    }

    private static LazyMan lazyMan = null;

    private static LazyMan getInstance(){
        if (lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

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

}

但是這種方式很容易看出在多線程中是不安全的蕴纳。

考慮多個線程同時調(diào)用getInstance()方法并成功進(jìn)入了if的條件中,就會出現(xiàn)多個對象秒裕。

? Thread-0 is running constructor
? Thread-1 is running constructor

DCL懶漢式

使用雙重判斷,并加鎖的方法钞啸。

樣例:

public class DCLLazy {
    //雙重檢測

    private DCLLazy(){
        System.out.println(Thread.currentThread().getName()+" is running constructor");
    }

    private static DCLLazy dclLazy = null;

    private static DCLLazy getInstance(){
        if (dclLazy == null){
            synchronized (DCLLazy.class){
                if (dclLazy == null){
                    dclLazy = new DCLLazy();
                }
            }
        }
        return dclLazy;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                getInstance();
            }).start();
        }
    }
}
  • 第一次判斷是判斷當(dāng)前是否有對象几蜻,這里并不會有線程安全問題。(如果有体斩,多個線程都返回對象梭稚,如果沒有多個線程進(jìn)入內(nèi)部嘗試創(chuàng)建對象)
  • 加鎖確保只會有一個線程進(jìn)入去創(chuàng)建對象
  • 第二次判斷,因?yàn)槌顺晒?chuàng)建對象的那個線程絮吵,其他線程都在阻塞等待弧烤。而等待到了鎖也沒有創(chuàng)建的必要了,為了防止多次創(chuàng)建這里再進(jìn)行一次判斷

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

public class SLazy {
    //靜態(tài)內(nèi)部類

    private SLazy(){
        System.out.println(Thread.currentThread().getName()+" is running constructor");
    }

    private static class SLazyHolder{
        private static final SLazy instance = new SLazy();
    }

    public static SLazy getInstance(){
        return SLazyHolder.instance;
    }

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

DCL和靜態(tài)內(nèi)部類的特點(diǎn)是類似的蹬敲,保證了線程安全暇昂,會受指令重排影響

其實(shí)這樣的DCL和靜態(tài)內(nèi)部類也還是有問題的,問題在于指令重排序伴嗡。

真正安全的單例方式應(yīng)該對單例對象加上volatile關(guān)鍵字急波。(這是volatile禁止指令重排的重要應(yīng)用,務(wù)必記妆裥!)

new方法并不是原子操作澄暮。大致有以下三步

  1. 申請內(nèi)存空間
  2. 調(diào)用構(gòu)造器方法
  3. 將對象指向內(nèi)存

而實(shí)際上因?yàn)橹噶钪嘏诺脑蛎危襟E2,3順序可能顛倒泣懊。導(dǎo)致對象指向了一個沒有成功構(gòu)造的內(nèi)存伸辟。而其他線程會認(rèn)為已經(jīng)構(gòu)造,導(dǎo)致調(diào)用沒有構(gòu)造的對象馍刮。

反射破解

使用如下代碼就可以跨過getInstance()方法直接構(gòu)造

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Class clazz = DCLLazy.getInstance().getClass();
    Constructor declaredConstructor = clazz.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);

    DCLLazy instance1 = (DCLLazy) declaredConstructor.newInstance();
    DCLLazy instance2 = (DCLLazy) declaredConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}
main is running constructor
main is running constructor
main is running constructor
com.baidu.ai.aip.auth.single.DCLLazy@74a14482
com.baidu.ai.aip.auth.single.DCLLazy@1540e19d


可以看到對象被構(gòu)建了三次


那么對構(gòu)造函數(shù)進(jìn)行如下升級

private DCLLazy(){
    synchronized (DCLLazy.class){
        if(dclLazy != null){
            throw new RuntimeException("請不要使用反射進(jìn)行創(chuàng)建");
        }
    }
    System.out.println(Thread.currentThread().getName()+" is running constructor");
}

再次嘗試

main is running constructor
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.baidu.ai.aip.auth.single.DCLLazy.main(DCLLazy.java:37)
Caused by: java.lang.RuntimeException: 請不要使用反射進(jìn)行創(chuàng)建
    at com.baidu.ai.aip.auth.single.DCLLazy.<init>(DCLLazy.java:13)
    ... 5 more

可以看到這種方式已經(jīng)行不通了信夫。

但如果不通過單例對象獲取字節(jié)碼,直接通過字節(jié)碼得到構(gòu)造方法進(jìn)行構(gòu)造渠退,這種方式仍然是會被破解的忙迁。

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // Class clazz = DCLLazy.getInstance().getClass();
    Constructor declaredConstructor = DCLLazy.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);

    DCLLazy instance1 = (DCLLazy) declaredConstructor.newInstance();
    DCLLazy instance2 = (DCLLazy) declaredConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);

}    

可以看到成功創(chuàng)建了兩個對象

main is running constructor
main is running constructor
com.baidu.ai.aip.auth.single.DCLLazy@74a14482
com.baidu.ai.aip.auth.single.DCLLazy@1540e19d

總結(jié):無論是增加判斷,鎖碎乃,還是私有屬性進(jìn)行檢查姊扔,都沒辦法阻止反射通過獲取私有構(gòu)造器,并直接構(gòu)造梅誓。

枚舉類型

枚舉類型相比之前的方式最大的優(yōu)點(diǎn)在于不會被反射破解恰梢。

public enum EDemo {

    INSTANCE;
    
    public EDemo getInstance(){
        return INSTANCE;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市梗掰,隨后出現(xiàn)的幾起案子嵌言,更是在濱河造成了極大的恐慌,老刑警劉巖及穗,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摧茴,死亡現(xiàn)場離奇詭異,居然都是意外死亡埂陆,警方通過查閱死者的電腦和手機(jī)苛白,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焚虱,“玉大人购裙,你說我怎么就攤上這事【樵裕” “怎么了躏率?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長民鼓。 經(jīng)常有香客問我薇芝,道長,這世上最難降的妖魔是什么丰嘉? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任恩掷,我火速辦了婚禮,結(jié)果婚禮上供嚎,老公的妹妹穿的比我還像新娘黄娘。我一直安慰自己峭状,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布逼争。 她就那樣靜靜地躺著优床,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誓焦。 梳的紋絲不亂的頭發(fā)上胆敞,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音杂伟,去河邊找鬼移层。 笑死,一個胖子當(dāng)著我的面吹牛赫粥,可吹牛的內(nèi)容都是我干的观话。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼越平,長吁一口氣:“原來是場噩夢啊……” “哼频蛔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秦叛,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤晦溪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挣跋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體三圆,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年避咆,在試婚紗的時候發(fā)現(xiàn)自己被綠了舟肉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡牌借,死狀恐怖度气,靈堂內(nèi)的尸體忽然破棺而出割按,到底是詐尸還是另有隱情膨报,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布适荣,位于F島的核電站现柠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弛矛。R本人自食惡果不足惜够吩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丈氓。 院中可真熱鬧周循,春花似錦强法、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚎研,卻和暖如春蓖墅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背临扮。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工论矾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杆勇。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓贪壳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親靶橱。 傳聞我的和親對象是個殘疾皇子寥袭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

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