1. 程式人生 > >設計模式之單例模式總結

設計模式之單例模式總結

常見的實現單例的方法大致分為五種
  1. * 餓漢模式
  2. * 懶漢模式
  3. * 雙重檢查鎖
  4. * 內部類
  5. * 列舉

1. 餓漢模式
public class Single1 {
    private static Single1 single = new Single1();
    private Single1() {
    }
    public static Single1 getInstance(){

            return single;

    }
}

2. 懶漢模式
#2.1
 * 懶漢模式
 * 執行緒不安全【延遲載入】

public class Single2_1 {
    private static Single2_1 single;
    private Single2_1() {
    }
    public static Single2_1 getInstance(){
        if(single == null){
            single = new Single2_1();
        }
        return single;
    }
}

#2.2
 * 懶漢模式
 * 執行緒安全【同步,延遲載入】

public class Single2_2 {
    private static Single2_2 single;
    private Single2_2() {
    }
    public static synchronized Single2_2 getInstance(){
        if(single == null){
            single = new Single2_2();
        }
        return single;
    }
}

3. 雙重監測鎖(懶漢模式)
 * 執行緒安全【同步,延遲載入】

 public class Single3 {
    private static Single3 single;
    private Single3() {
    }
    public static Single3 getInstance(){
        if(single == null){
            synchronized (Single3.class) {
                if(single == null){
                    single = new Single3();
                }   
            }
        }
        return single;
    }
}

4. 內部類
 * 執行緒安全【延遲載入】

 public class Single4 {
    private static class SingleHolder{
        private static final Single4 single = new Single4();
    }
    private Single4() {
    }
    public static Single4 getInstance(){
        return SingleHolder.single;
    }
}

5. 列舉
 * 執行緒安全【餓漢】
 * JVM底層支援
 * 反射,序列化對其無效

public enum Single5 {
    INSTANCE;
    public void operation(){
        System.out.println("其它方法");
    }
}

以上單例的實現方式除列舉外都可以通過反射和反序列化的方式進行破解!
通過列舉方式實現的單例模式是JVM底層天然實現的單例,反射無法破解!


反射攻擊單例模式
public class ReflectAttack {
    public static void main(String[] args) throws Exception {
        //理想中唯一的單例
        Single1 s1 = Single1.getInstance();
        
        Class<Single1> clazz = (Class<Single1>) Class.forName("com.xy.single.Single1");
        //反射出來的多例
        Single1 s2 = clazz.newInstance();
        System.out.println(s1==s2);
    }
}

##列印結果:false

>解決方法,在建構函式中做文章!【如果想通過反射破壞單例則丟擲執行時異常】
    private Single1() {
        if(single!=null)
            throw new RuntimeException();
    }

序列化攻擊單例模式【前提單例類要實現Serializable介面】

public class SerializeAttack {
    public static void main(String[] args) throws Exception {
        //理想中的單例
        Single1 single1 = Single1.getInstance();
        //將物件序列化
        FileOutputStream fos = new FileOutputStream("G:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(single1);
        oos.close();
        fos.close();
        //反序列化物件完成破解單例
        FileInputStream fis = new FileInputStream("G:/a.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Single1 single2 = (Single1) ois.readObject();
        ois.close();
        fis.close();        
        System.out.println(single1==single2);
    }
}

##列印結果:false
>解決方法,單例類中新增readResolver方法
    private Object readResolve() throws ObjectStreamException{
        return single;
    }

>該方法在ObjectReader呼叫readObject方法的時候觸發【基於回撥的】
>注意返回值是Object型別,可能會拋異常