設計模式之單例模式

什麼是單例設計模式?

單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。

具體實現

需要:

(1)將構造方法私有化,使其不能在類的外部通過new關鍵字實例化該類對象。

(2)在該類內部產生一個唯一的實例化對象,並且將其封裝爲private static類型。

(3)定義一個靜態方法返回這個唯一對象。

實現一:立即加載 / “餓漢模式”

立即加載就是使用類的時候已經將對象創建完畢(不管以後會不會使用到該實例化對象,先創建了再說。很着急的樣子,故又被稱爲“餓漢模式”),常見的實現辦法就是直接new實例化。

package com.Geeksun.singleton;

/**
 * “餓漢模式”的優缺點:
 *
 * 優點:實現起來簡單,沒有多線程同步問題。
 *
 * 缺點:當類SingletonTest被加載的時候,會初始化static的instance,靜態變量被創建並分配內存空間,從這以後,這個static
 * 的instance對象便一直佔着這段內存(即便你還沒有用到這個實例),當類被卸載時,靜態變量被摧毀,並釋放所佔有的內存,
 * 因此在某些特定條件下會耗費內存。
 */
public class Singleton01 {
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){}

    public static Singleton01 getInstance(){
        return instance;
    }
}

實現二:延遲加載 / “懶漢模式”

延遲加載就是調用get()方法時實例才被創建(先不急着實例化出對象,等要用的時候纔給你創建出來。不着急,故又稱爲“懶漢模式”),常見的實現方法就是在get方法中進行new實例化。

package com.Geeksun.singleton;

/**
 * “懶漢模式”的優缺點:
 *
 * 優點:1.在多線程情形下,保證了“懶漢模式”的線程安全。
 *       2.實現起來比較簡單,當類SingletonTest被加載的時候,靜態變量static的instance未被創建並分配內存空間,當getInstance方法
 *       第一次被調用時,初始化instance變量,並分配內存,因此在某些特定條件下會節約了內存。
 *
 * 缺點:衆所周知在多線程情形下,synchronized方法通常效率低,顯然這不是最佳的實現方案。
 */
public class Singleton02 {
    private static Singleton02 instance;

    private Singleton02(){}

    public static synchronized Singleton02 getInstance() {
        if(instance == null){
            instance = new Singleton02();
        }
        return instance;
    }
}

實現三:DCL雙檢查鎖機制(DCL:double checked locking)

package com.Geeksun.singleton;

/**
 * “雙重檢查鎖”的優缺點:
 *
 * 雙重檢查鎖定背後的理論是完美的。不幸地是,現實完全不同。雙重檢查鎖定的問題是:並不能保證它會在單處理器或多處理器計算機上順利運行。
 *
 * 雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺內存模型。內存模型允許所謂的“無序寫入”,這也是這些習語失敗的一個主要原因。
 */
public class Singleton03 {
    private volatile static Singleton03 instance;//使用了volatile關鍵字後,重排序被禁止,所有的寫(write)操作都將發生在讀(read)操作之前。

    private Singleton03(){}

    public static Singleton03 getInstance() {
        if(instance == null){
            synchronized (Singleton03.class){
                if(instance == null){
                    instance = new Singleton03();
                }
            }
        }
        return instance;
    }
}

實現四:靜態內部類

package com.Geeksun.singleton;

/**
 * 靜態內部類
 *註解:定義一個私有的內部類,在第一次用這個嵌套類時,會創建一個實例。而類型爲SingletonHolder的類,只有在
 * Singleton.getInstance()中調用,由於私有的屬性,他人無法使用SingleHolder,不調用Singleton.getInstance()就不會創建實例。
 * 優點:達到了lazy loading的效果,即按需創建實例。
 * 
 */
public class Singleton04 {

    private Singleton04(){
    }

    private static class SingletonHolder{
        private static final Singleton04 instance = new Singleton04();
    }

    public Singleton04 getInstance(){
        return SingletonHolder.instance;
    }
}

實現五:枚舉類

package com.Geeksun.singleton;

/**
 * “枚舉類”
 *  這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,
 *   可謂是很堅強的壁壘啊,書寫較爲方便。
 *
 */
public enum  Singleton05 {
    INSTANCE;

    public void operation(){

    }
}

 

除了枚舉類外,單例模式可以被破解

package com.Geeksun.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

public class Client {

    public static void main(String[] args) throws Exception {
        //通過反射破解單例模式
        Singleton01 sc1 = Singleton01.getInstance();
        Singleton01 sc2 = Singleton01.getInstance();
        Class<Singleton01> clazz = (Class<Singleton01>)Class.forName("com.Geeksun.singleton.Singleton01");
        Constructor<Singleton01> c = clazz.getDeclaredConstructor();
        c.setAccessible(true);
        Singleton01 sc3 = c.newInstance();
        Singleton01 sc4 = c.newInstance();
        System.out.println(sc1);
        System.out.println(sc2);
        System.out.println(sc3);
        System.out.println(sc4);
        
        //通過反序列化破解單例模式
        FileOutputStream fis = new FileOutputStream("D:/test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fis);
        oos.writeObject(sc1);
        oos.close();
        fis.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/test.txt"));
        Singleton01 sc5 = (Singleton01)ois.readObject();
        System.out.println(sc1);
        System.out.println(sc5);
    }
}

對於餓漢模式,防止破解的方法是

public class Singleton01 implements Serializable {
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){
        //如果已經創建了對象,則不能調用無參構造函數!
        if(instance != null){
            throw new RuntimeException();
        }
    }

    public static Singleton01 getInstance(){
        return instance;
    }

    //反序列化時,如果定義了readResolve()則直接返回該方法指定的對象,不需要指定新的對象。
    private Object readResolve(){
        return instance;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章