成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java實(shí)現(xiàn)單例的幾種方式

zengdongbao / 2769人閱讀

摘要:?jiǎn)卫J降膽袧h實(shí)現(xiàn)線程安全通過(guò)設(shè)置同步方法,效率太低,整個(gè)方法被加鎖模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作使用上面的測(cè)試類,測(cè)試結(jié)果可以看到,這種方式達(dá)到了線程安全??梢哉f(shuō)這種方式是實(shí)現(xiàn)單例模式的最優(yōu)解。

1. 什么是單例模式

單例模式指的是在應(yīng)用整個(gè)生命周期內(nèi)只能存在一個(gè)實(shí)例。單例模式是一種被廣泛使用的設(shè)計(jì)模式。他有很多好處,能夠避免實(shí)例對(duì)象的重復(fù)創(chuàng)建,減少創(chuàng)建實(shí)例的系統(tǒng)開銷,節(jié)省內(nèi)存。

2. 單例模式和靜態(tài)類的區(qū)別

首先理解一下什么是靜態(tài)類,靜態(tài)類就是一個(gè)類里面都是靜態(tài)方法和靜態(tài)field,構(gòu)造器被private修飾,因此不能被實(shí)例化。Math類就是一個(gè)靜態(tài)類。

知道了什么是靜態(tài)類后,來(lái)說(shuō)一下他們兩者之間的區(qū)別:

1)首先單例模式會(huì)提供給你一個(gè)全局唯一的對(duì)象,靜態(tài)類只是提供給你很多靜態(tài)方法,這些方法不用創(chuàng)建對(duì)象,通過(guò)類就可以直接調(diào)用;

2)如果是一個(gè)非常重的對(duì)象,單例模式可以懶加載,靜態(tài)類就無(wú)法做到;

那么時(shí)候時(shí)候應(yīng)該用靜態(tài)類,什么時(shí)候應(yīng)該用單例模式呢?首先如果你只是想使用一些工具方法,那么最好用靜態(tài)類,靜態(tài)類比單例類更快,因?yàn)殪o態(tài)的綁定是在編譯期進(jìn)行的。如果你要維護(hù)狀態(tài)信息,或者訪問資源時(shí),應(yīng)該選用單例模式。還可以這樣說(shuō),當(dāng)你需要面向?qū)ο蟮哪芰r(shí)(比如繼承、多態(tài))時(shí),選用單例類,當(dāng)你僅僅是提供一些方法時(shí)選用靜態(tài)類。

3.如何實(shí)現(xiàn)單例模式

1. 餓漢模式

所謂餓漢模式就是立即加載,一般情況下再調(diào)用getInstancef方法之前就已經(jīng)產(chǎn)生了實(shí)例,也就是在類加載的時(shí)候已經(jīng)產(chǎn)生了。這種模式的缺點(diǎn)很明顯,就是占用資源,當(dāng)單例類很大的時(shí)候,其實(shí)我們是想使用的時(shí)候再產(chǎn)生實(shí)例。因此這種方式適合占用資源少,在初始化的時(shí)候就會(huì)被用到的類。

class SingletonHungary {
    private static SingletonHungary singletonHungary = new SingletonHungary();
    //將構(gòu)造器設(shè)置為private禁止通過(guò)new進(jìn)行實(shí)例化
    private SingletonHungary() {
        
    }
    public static SingletonHungary getInstance() {
        return singletonHungary;
    }
}

2. 懶漢模式

懶漢模式就是延遲加載,也叫懶加載。在程序需要用到的時(shí)候再創(chuàng)建實(shí)例,這樣保證了內(nèi)存不會(huì)被浪費(fèi)。針對(duì)懶漢模式,這里給出了5種實(shí)現(xiàn)方式,有些實(shí)現(xiàn)方式是線程不安全的,也就是說(shuō)在多線程并發(fā)的環(huán)境下可能出現(xiàn)資源同步問題。

首先第一種方式,在單線程下沒問題,在多線程下就出現(xiàn)問題了。

// 單例模式的懶漢實(shí)現(xiàn)1--線程不安全
class SingletonLazy1 {
    private static SingletonLazy1 singletonLazy;

    private SingletonLazy1() {

    }

    public static SingletonLazy1 getInstance() {
        if (null == singletonLazy) {
            try {
                // 模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singletonLazy = new SingletonLazy1();
        }
        return singletonLazy;
    }
}

我們模擬10個(gè)異步線程測(cè)試一下:

public class SingletonLazyTest {

    public static void main(String[] args) {

        Thread2[] ThreadArr = new Thread2[10];
        for (int i = 0; i < ThreadArr.length; i++) {
            ThreadArr[i] = new Thread2();
            ThreadArr[i].start();
        }
    }

}

// 測(cè)試線程
class Thread2 extends Thread {
    @Override
    public void run() {
        System.out.println(SingletonLazy1.getInstance().hashCode());
    }
}

運(yùn)行結(jié)果:

124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303

可以看到他們的hashCode不都是一樣的,說(shuō)明在多線程環(huán)境下,產(chǎn)生了多個(gè)對(duì)象,不符合單例模式的要求。

那么如何使線程安全呢?第二種方法,我們使用synchronized關(guān)鍵字對(duì)getInstance方法進(jìn)行同步。

// 單例模式的懶漢實(shí)現(xiàn)2--線程安全
// 通過(guò)設(shè)置同步方法,效率太低,整個(gè)方法被加鎖
class SingletonLazy2 {
    private static SingletonLazy2 singletonLazy;

    private SingletonLazy2() {

    }

    public static synchronized SingletonLazy2 getInstance() {
        try {
            if (null == singletonLazy) {
                // 模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作
                Thread.sleep(1000);
                singletonLazy = new SingletonLazy2();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return singletonLazy;
    }
}

使用上面的測(cè)試類,測(cè)試結(jié)果:

1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989

可以看到,這種方式達(dá)到了線程安全。但是缺點(diǎn)就是效率太低,是同步運(yùn)行的,下個(gè)線程想要取得對(duì)象,就必須要等上一個(gè)線程釋放,才可以繼續(xù)執(zhí)行。

那我們可以不對(duì)方法加鎖,而是將里面的代碼加鎖,也可以實(shí)現(xiàn)線程安全。但這種方式和同步方法一樣,也是同步運(yùn)行的,效率也很低。

// 單例模式的懶漢實(shí)現(xiàn)3--線程安全
// 通過(guò)設(shè)置同步代碼塊,效率也太低,整個(gè)代碼塊被加鎖
class SingletonLazy3 {

    private static SingletonLazy3 singletonLazy;

    private SingletonLazy3() {

    }

    public static SingletonLazy3 getInstance() {
        try {
            synchronized (SingletonLazy3.class) {
                if (null == singletonLazy) {
                    // 模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作
                    Thread.sleep(1000);
                    singletonLazy = new SingletonLazy3();
                }
            }
        } catch (InterruptedException e) {
            // TODO: handle exception
        }
        return singletonLazy;
    }
}

我們來(lái)繼續(xù)優(yōu)化代碼,我們只給創(chuàng)建對(duì)象的代碼進(jìn)行加鎖,但是這樣能保證線程安全么?

// 單例模式的懶漢實(shí)現(xiàn)4--線程不安全
// 通過(guò)設(shè)置同步代碼塊,只同步創(chuàng)建實(shí)例的代碼
// 但是還是有線程安全問題
class SingletonLazy4 {

    private static SingletonLazy4 singletonLazy;

    private SingletonLazy4() {

    }

    public static SingletonLazy4 getInstance() {
        try {
            if (null == singletonLazy) {        //代碼1
                // 模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作
                Thread.sleep(1000);
                synchronized (SingletonLazy4.class) {
                    singletonLazy = new SingletonLazy4(); //代碼2
                }
            }
        } catch (InterruptedException e) {
            // TODO: handle exception
        }
        return singletonLazy;
    }
}

我們來(lái)看一下運(yùn)行結(jié)果:

1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303

從結(jié)果看來(lái),這種方式不能保證線程安全,為什么呢?我們假設(shè)有兩個(gè)線程A和B同時(shí)走到了‘代碼1’,因?yàn)榇藭r(shí)對(duì)象還是空的,所以都能進(jìn)到方法里面,線程A首先搶到鎖,創(chuàng)建了對(duì)象。釋放鎖后線程B拿到了鎖也會(huì)走到‘代碼2’,也創(chuàng)建了一個(gè)對(duì)象,因此多線程環(huán)境下就不能保證單例了。

讓我們來(lái)繼續(xù)優(yōu)化一下,既然上述方式存在問題,那我們?cè)谕酱a塊里面再一次做一下null判斷不就行了,這種方式就是我們的DCL雙重檢查鎖機(jī)制。

//單例模式的懶漢實(shí)現(xiàn)5--線程安全
//通過(guò)設(shè)置同步代碼塊,使用DCL雙檢查鎖機(jī)制
//使用雙檢查鎖機(jī)制成功的解決了單例模式的懶漢實(shí)現(xiàn)的線程不安全問題和效率問題
//DCL 也是大多數(shù)多線程結(jié)合單例模式使用的解決方案
class SingletonLazy5 {

    private static SingletonLazy5 singletonLazy;

    private SingletonLazy5() {

    }

    public static SingletonLazy5 getInstance() {
        try {
            if (null == singletonLazy) {
                // 模擬在創(chuàng)建對(duì)象之前做一些準(zhǔn)備工作
                Thread.sleep(1000);
                synchronized (SingletonLazy5.class) {
                    if(null == singletonLazy) {
                        singletonLazy = new SingletonLazy5();
                    }
                }
            }
        } catch (InterruptedException e) {
            // TODO: handle exception
        }
        return singletonLazy;
    }
}

運(yùn)行結(jié)果:

124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239

我們可以看到DCL雙重檢查鎖機(jī)制很好的解決了懶加載單例模式的效率問題和線程安全問題。這也是我們最常用到的方式。

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

我們也可以使用靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式,代碼如下:

//使用靜態(tài)內(nèi)部類實(shí)現(xiàn)單例模式--線程安全
class SingletonStaticInner {
    private SingletonStaticInner() {
        
    }
    private static class SingletonInner {
        private static SingletonStaticInner singletonStaticInner = new SingletonStaticInner();
    }
    public static SingletonStaticInner getInstance() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return SingletonInner.singletonStaticInner;
    }
}

可以看到使用這種方式我們沒有顯式的進(jìn)行任何同步操作,那他是如何保證線程安全呢?和餓漢模式一樣,是靠JVM保證類的靜態(tài)成員只能被加載一次的特點(diǎn),這樣就從JVM層面保證了只會(huì)有一個(gè)實(shí)例對(duì)象。那么問題來(lái)了,這種方式和餓漢模式又有什么區(qū)別呢?不也是立即加載么?實(shí)則不然,加載一個(gè)類時(shí),其內(nèi)部類不會(huì)同時(shí)被加載。一個(gè)類被加載,當(dāng)且僅當(dāng)其某個(gè)靜態(tài)成員(靜態(tài)域、構(gòu)造器、靜態(tài)方法等)被調(diào)用時(shí)發(fā)生。

可以說(shuō)這種方式是實(shí)現(xiàn)單例模式的最優(yōu)解。

4. 靜態(tài)代碼塊

這里提供了靜態(tài)代碼塊實(shí)現(xiàn)單例模式。這種方式和第一種類似,也是一種餓漢模式。

//使用靜態(tài)代碼塊實(shí)現(xiàn)單例模式
class SingletonStaticBlock {
    private static SingletonStaticBlock singletonStaticBlock;
    static {
        singletonStaticBlock = new SingletonStaticBlock();
    }
    public static SingletonStaticBlock getInstance() {
        return singletonStaticBlock;
    }
}

5. 序列化與反序列化

LZ為什么要提序列化和反序列化呢?因?yàn)閱卫J诫m然能保證線程安全,但在序列化和反序列化的情況下會(huì)出現(xiàn)生成多個(gè)對(duì)象的情況。運(yùn)行下面的測(cè)試類,

public class SingletonStaticInnerSerializeTest {

    public static void main(String[] args) {
        try {
            SingletonStaticInnerSerialize serialize = SingletonStaticInnerSerialize.getInstance();
            System.out.println(serialize.hashCode());
            //序列化
            FileOutputStream fo = new FileOutputStream("tem");
            ObjectOutputStream oo = new ObjectOutputStream(fo);
            oo.writeObject(serialize);
            oo.close();
            fo.close();
            //反序列化
            FileInputStream fi = new FileInputStream("tem");
            ObjectInputStream oi = new ObjectInputStream(fi);
            SingletonStaticInnerSerialize serialize2 = (SingletonStaticInnerSerialize) oi.readObject();
            oi.close();
            fi.close();
            System.out.println(serialize2.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

//使用匿名內(nèi)部類實(shí)現(xiàn)單例模式,在遇見序列化和反序列化的場(chǎng)景,得到的不是同一個(gè)實(shí)例
//解決這個(gè)問題是在序列化的時(shí)候使用readResolve方法,即去掉注釋的部分
class SingletonStaticInnerSerialize implements Serializable {
    
    /**
     * 2018年03月28日
     */
    private static final long serialVersionUID = 1L;
    
    private static class InnerClass {
        private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
    }
    
    public static SingletonStaticInnerSerialize getInstance() {
        return InnerClass.singletonStaticInnerSerialize;
    }
    
//    protected Object readResolve() {
//        System.out.println("調(diào)用了readResolve方法");
//        return InnerClass.singletonStaticInnerSerialize;
//    }
}

可以看到:

865113938
1078694789

結(jié)果表明的確是兩個(gè)不同的對(duì)象實(shí)例,違背了單例模式,那么如何解決這個(gè)問題呢?解決辦法就是在反序列化中使用readResolve()方法,將上面的注釋代碼去掉,再次運(yùn)行:

865113938
調(diào)用了readResolve方法
865113938

問題來(lái)了,readResolve()方法到底是何方神圣,其實(shí)當(dāng)JVM從內(nèi)存中反序列化地"組裝"一個(gè)新對(duì)象時(shí),就會(huì)自動(dòng)調(diào)用這個(gè) readResolve方法來(lái)返回我們指定好的對(duì)象了, 單例規(guī)則也就得到了保證。readResolve()的出現(xiàn)允許程序員自行控制通過(guò)反序列化得到的對(duì)象。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71069.html

相關(guān)文章

  • JAVA單例(Singleton)實(shí)現(xiàn)幾種方式(多線程安全)

    摘要:缺點(diǎn)每次調(diào)用都有線程開銷延遲初始化單例默認(rèn)構(gòu)造方法為,避免用戶用構(gòu)造出新對(duì)象獲取單例的靜態(tài)工廠同步方法延遲初始化單例使用同步方法保證多線程操作只實(shí)例化一個(gè)實(shí)力單例模式。 主要分為兩種: 直接初始化 延遲初始化 直接初始化 直接初始化final靜態(tài)成員 線程安全:JVM保證final靜態(tài)成員只會(huì)被初始化一次 公有靜態(tài)成員是個(gè)final域,直接引用成員獲取單例 /** * 公有靜態(tài)成...

    smartlion 評(píng)論0 收藏0
  • 深入理解單例模式

    摘要:總結(jié)我們主要介紹到了以下幾種方式實(shí)現(xiàn)單例模式餓漢方式線程安全懶漢式非線程安全和關(guān)鍵字線程安全版本懶漢式雙重檢查加鎖版本枚舉方式參考設(shè)計(jì)模式中文版第二版設(shè)計(jì)模式深入理解單例模式我是一個(gè)以架構(gòu)師為年之內(nèi)目標(biāo)的小小白。 初遇設(shè)計(jì)模式在上個(gè)寒假,當(dāng)時(shí)把每個(gè)設(shè)計(jì)模式過(guò)了一遍,對(duì)設(shè)計(jì)模式有了一個(gè)最初級(jí)的了解。這個(gè)學(xué)期借了幾本設(shè)計(jì)模式的書籍看,聽了老師的設(shè)計(jì)模式課,對(duì)設(shè)計(jì)模式算是有個(gè)更進(jìn)一步的認(rèn)識(shí)。...

    FuisonDesign 評(píng)論0 收藏0
  • 單例模式你會(huì)幾種寫法?

    摘要:使用靜態(tài)類體現(xiàn)的是基于對(duì)象,而使用單例設(shè)計(jì)模式體現(xiàn)的是面向?qū)ο?。二編寫單例模式的代碼編寫單例模式的代碼其實(shí)很簡(jiǎn)單,就分了三步將構(gòu)造函數(shù)私有化在類的內(nèi)部創(chuàng)建實(shí)例提供獲取唯一實(shí)例的方法餓漢式根據(jù)上面的步驟,我們就可以輕松完成創(chuàng)建單例對(duì)象了。 前言 只有光頭才能變強(qiáng) 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡(jiǎn)單啦 本來(lái)打算沒那么快更新的,這陣子在刷Spring的書籍。在看...

    solocoder 評(píng)論0 收藏0
  • 【備戰(zhàn)春招/秋招系列】美團(tuán)面經(jīng)總結(jié)基礎(chǔ)篇 (附詳解答案)

    摘要:不同于個(gè)人面經(jīng),這份面經(jīng)具有普適性。我在前面的文章中也提到了應(yīng)該怎么做自我介紹與項(xiàng)目介紹,詳情可以查看這篇文章備戰(zhàn)春招秋招系列初出茅廬的程序員該如何準(zhǔn)備面試。是建立連接時(shí)使用的握手信號(hào)。它表示確認(rèn)發(fā)來(lái)的數(shù)據(jù)已經(jīng)接受無(wú)誤。 showImg(https://segmentfault.com/img/remote/1460000016972448?w=921&h=532); 該文已加入開源文...

    Leck1e 評(píng)論0 收藏0
  • JS設(shè)計(jì)模式-單例模式

    摘要:?jiǎn)卫J絾卫J剑步袉巫幽J?,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。透明的單例模式利用一個(gè)形成一個(gè)閉包,在里邊通過(guò)變量來(lái)記錄實(shí)例,并返回構(gòu)造函數(shù)。 單例模式 單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。 ———來(lái)自維基百科 一個(gè)很典型的應(yīng)用是在點(diǎn)擊登錄按鈕,彈出登錄浮窗,不論...

    Doyle 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<