中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

深入理解單例模式(上)

2018-07-31    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

最近在閱讀《Effective Java 》這本書,第3個(gè)條款專門提到了單例屬性,并給出了使用單例的最佳實(shí)踐建議。讓我對(duì)這個(gè)單例模式(原本我以為是設(shè)計(jì)模式中最簡(jiǎn)單的一種)有了更深的認(rèn)識(shí)。

單例模式

單例模式(Singleton Pattern)是 Java 中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。

在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)的全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。

單例的特點(diǎn)

  1. 單例類只能有一個(gè)實(shí)例。
  2. 單例類必須自己創(chuàng)建自己的唯一實(shí)例。
  3. 單例類必須給所有其他對(duì)象提供這一實(shí)例。

單例模式的7種寫法

單例模式的寫法很多,涉及到了線程安全和性能問(wèn)題。在這里我不重復(fù)介紹。這篇《單例模式的七種寫法》寫得很詳細(xì),博主也給出了每一種寫法的優(yōu)缺點(diǎn)。

但是,單例模式真的能夠?qū)崿F(xiàn)實(shí)例的唯一性嗎?答案是否定的。

如何破壞單例

反射

有兩種常見的方式來(lái)實(shí)現(xiàn)單例。他們的做法都是將構(gòu)造方法設(shè)為私有,并導(dǎo)出一個(gè)公有的靜態(tài)成員來(lái)提供對(duì)唯一實(shí)例的訪問(wèn)。在第1種方式中,成員是個(gè)final字段:

// Singleton with public final field
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public void leaveTheBuilding() { ... }
}

只調(diào)用私有構(gòu)造函數(shù)一次,以初始化公共靜態(tài)final字段elvi.instance。不提供公有的或者受保護(hù)的構(gòu)造函數(shù)保證了全局唯一性:當(dāng)Elvis類初始化的時(shí)候,僅僅只會(huì)有一個(gè)Elvis實(shí)例存在——不多也不少 。無(wú)論客戶端怎么做都無(wú)法改變這一點(diǎn),只不過(guò)我還是要警告一下 :授權(quán)的客戶端可以通過(guò)反射來(lái)調(diào)用私有構(gòu)造方法,借助于AccessibleObject.setAccessible方法即可做到 。如果需要防范這種攻擊,請(qǐng)修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個(gè)實(shí)例時(shí)拋出異常。

測(cè)試代碼:

public class TestSingleton {

    /**
     * 通過(guò)反射破壞單例
     */
    @Test
    public void testReflection() throws Exception {
        /**
         * 驗(yàn)證單例有效性
         */
        Elvis elvis1 = Elvis.INSTANCE;
        Elvis elvis2 = Elvis.INSTANCE;

        System.out.println("elvis1 == elvis2 ? ===>" + (elvis1 == elvis2));
        System.err.println("-----------------");

        /**
         * 反射調(diào)用構(gòu)造方法
         */
        Class clazz = Elvis.class;
        Constructor cons = clazz.getDeclaredConstructor(null); 
        cons.setAccessible(true);

        Elvis elvis3 = (Elvis) cons.newInstance(null);

        System.out.println("elvis1 == elvis3 ? ===> "
            + (elvis1 == elvis3));
    }
}

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

Elvis Constructor is invoked!
elvis1 == elvis2 ? ===> true
elvis1 == elvis3 ? ===> false
-----------------
Elvis Constructor is invoked!

結(jié)論:

反射是可以破壞單例屬性的。因?yàn)槲覀兺ㄟ^(guò)反射把它的構(gòu)造函數(shù)設(shè)成可訪問(wèn)的,然后去生成一個(gè)新的對(duì)象。

改進(jìn)版的單例寫法:

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        System.err.println("Elvis Constructor is invoked!");
        if (INSTANCE != null) {
            System.err.println("實(shí)例已存在,無(wú)法初始化!");
            throw new UnsupportedOperationException("實(shí)例已存在,無(wú)法初始化!");
        }
    }

}

結(jié)果:

Elvis Constructor is invoked!
elvis1 == elvis2 ? ===> true
-----------------
Elvis Constructor is invoked!
實(shí)例已存在,無(wú)法初始化!

第2種實(shí)現(xiàn)單例模式的方法是,提供一個(gè)公有的靜態(tài)工廠方法:

// Singleton with static factory
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance() { return INSTANCE; }
    public void leaveTheBuilding() { ... }
}

所有調(diào)用Elvis類的getInstance方法,返回相同的對(duì)象引用,并且不會(huì)有其它的Elvis對(duì)象被創(chuàng)建。但同樣有上面第1個(gè)方法提到的反射破壞單例屬性的問(wèn)題存在。

序列化和反序列化

如果對(duì)上述2種方式實(shí)現(xiàn)的單例類進(jìn)行序列化,反序列化得到的對(duì)象是否是同一個(gè)對(duì)象呢?答案是否定的。
看下面的測(cè)試代碼:
單例類:

public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        System.err.println("Elvis Constructor is invoked!");
    }

}

測(cè)試代碼:

    /**
     * 序列化對(duì)單例屬性的影響
     * @throws Exception 
     */
    @Test
    public void testSerialization() throws Exception {
        Elvis elvis1 = Elvis.INSTANCE;
        FileOutputStream fos = new FileOutputStream("a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(elvis1);
        oos.flush();
        oos.close();

        Elvis elvis2 = null;
        FileInputStream fis = new FileInputStream("a.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        elvis2 = (Elvis) ois.readObject();

        System.out.println("elvis1 == elvis2 ? ===>" + (elvis1 == elvis2));
    }

結(jié)果是:

Elvis Constructor is invoked! 
elvis1 == elvis2 ? ===>false

說(shuō)明:

通過(guò)對(duì)序列化后的Elvis 進(jìn)行反序列化得到的對(duì)象是一個(gè)新的對(duì)象,這就破壞了Elvis 的單例性。

標(biāo)簽: 安全 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:TurnKey Linux 15.0,基于 Debian 的虛擬應(yīng)用程序庫(kù)

下一篇:MYSQL事務(wù)隔離級(jí)別