1. 程式人生 > 其它 >設計模式-建立者模式-單例模式(存在的問題以及解決方案)

設計模式-建立者模式-單例模式(存在的問題以及解決方案)

一、 存在的問題

1. 問題演示

破壞單例模式:

使上面定義的單例類(Singleton)可以建立多個物件,列舉方式除外。有兩種方式,分別是序列化和反射。

  • 序列化反序列化
    Singleton類:

必須要實現Serializable

一個物件序列化的介面,一個類只有實現了Serializable介面,它的物件才能被序列化

package com.mangoubiubiu.designpattern.single;

import java.io.Serializable;

/**
 * 餓漢式
 *      靜態變數建立類的物件
 */
public class
SingleEH implements Serializable { private SingleEH(){} //在成員變數建立物件 private static SingleEH singleEH =new SingleEH(); //提供對外訪問的靜態方法 public static SingleEH getSingleEH(){ return singleEH; } }
package com.mangoubiubiu.designpattern.single;

import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class DestorySingle { public static void main(String[] args) throws Exception{ writeSingle(); readSingle(); } //從檔案中讀資料 public static void readSingle() throws
Exception{ SingleEH singleEH=SingleEH.getSingleEH(); ObjectInputStream inputStream =new ObjectInputStream(new FileInputStream("D:\\single.txt")); SingleEH singleObject = (SingleEH)inputStream.readObject(); System.out.println(singleEH); System.out.println(singleObject); inputStream.close(); } //向檔案中寫入資料 public static void writeSingle() throws Exception{ SingleEH singleEH=SingleEH.getSingleEH(); ObjectOutputStream outputStream =new ObjectOutputStream(new FileOutputStream("D:\\single.txt")); outputStream.writeObject(singleEH); outputStream.close(); } }

執行結果

不是同一物件,說明序列化和反序列化破壞了單列模式

 

反射

Singleton類:

package com.mangoubiubiu.designpattern.single;

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

public class DestorySingle {


    public static void main(String[] args)  throws Exception{
        //獲取SingleEH的位元組碼物件
        Class clazz=SingleEH.class;
        //2,獲取無參構造方法
        Constructor constructor=clazz.getDeclaredConstructor();
        //3,取消訪問檢查
        constructor.setAccessible(true);
        //建立物件
        SingleEH clazzSingleEH=(SingleEH)constructor.newInstance();
        SingleEH singleEH=SingleEH.getSingleEH();
        System.out.println(singleEH);
        System.out.println(clazzSingleEH);
    }
}

執行結果

不是同一物件,說明反射破壞了單列模式

二、 解決方案

序列化、反序列方式破壞單例模式的解決方法

在Singleton類中新增readResolve()方法,在反序列化時被反射呼叫,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新new出來的物件。

package com.mangoubiubiu.designpattern.single;

import java.io.Serializable;

/**
 * 餓漢式
 *      靜態變數建立類的物件
 */
public class SingleEH implements Serializable {

    private SingleEH(){}

    //在成員變數建立物件
    private static SingleEH singleEH =new SingleEH();

    //提供對外訪問的靜態方法
    public static SingleEH getSingleEH(){
        return singleEH;
    }
    /**
     * 下面是為了解決序列化反序列化破解單例模式
     */
    private Object readResolve() {
        return singleEH;
    }

}
package com.mangoubiubiu.designpattern.single;

import java.io.*;
import java.lang.reflect.Constructor;

public class DestorySingle {


    public static void main(String[] args) throws Exception {
      //  writeSingle();
      readSingle();
    }


    public static void reflectSingle()throws Exception{
        //獲取SingleEH的位元組碼物件
        Class clazz=SingleEH.class;
        //2,獲取無參構造方法
        Constructor constructor=clazz.getDeclaredConstructor();
        //3,取消訪問檢查
        constructor.setAccessible(true);
        //建立物件
        SingleEH clazzSingleEH=(SingleEH)constructor.newInstance();
        SingleEH singleEH=SingleEH.getSingleEH();
        System.out.println(singleEH);
        System.out.println(clazzSingleEH);
    }

    //從檔案中讀資料
   public static void readSingle() throws Exception{
       SingleEH singleEH=SingleEH.getSingleEH();

       ObjectInputStream inputStream =new ObjectInputStream(new FileInputStream("D:\\single.txt"));
       SingleEH singleObject = (SingleEH)inputStream.readObject();
       System.out.println(singleEH);
       System.out.println(singleObject);
       inputStream.close();
   }



    //向檔案中寫入資料
    public static void writeSingle() throws Exception{
        File file =new File("D:\\single.txt");
        if(file.exists()){
            System.out.println("檔案已存在---準備刪除檔案");
            file.delete();
        }
        SingleEH singleEH=SingleEH.getSingleEH();
        ObjectOutputStream outputStream =new ObjectOutputStream(new FileOutputStream("D:\\single.txt"));
        outputStream.writeObject(singleEH);
        outputStream.close();
    }



}

 

發現為同一物件

原始碼解析:

ObjectInputStream類

public final Object readObject() throws IOException, ClassNotFoundException{
    ...
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);//重點檢視readObject0方法
    .....
}
    
private Object readObject0(boolean unshared) throws IOException {
    ...
    try {
        switch (tc) {
            ...
            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));//重點檢視readOrdinaryObject方法
            ...
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }    
}
    
private Object readOrdinaryObject(boolean unshared) throws IOException {
    ...
    //isInstantiable 返回true,執行 desc.newInstance(),通過反射建立新的單例類,
    obj = desc.isInstantiable() ? desc.newInstance() : null; 
    ...
    // 在Singleton類中新增 readResolve 方法後 desc.hasReadResolveMethod() 方法執行結果為true
    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        // 通過反射呼叫 Singleton 類中的 readResolve 方法,將返回值賦值給rep變數
        // 這樣多次呼叫ObjectInputStream類中的readObject方法,繼而就會呼叫我們定義的readResolve方法,所以返回的是同一個物件。
        Object rep = desc.invokeReadResolve(obj);
         ...
    }
    return obj;
}

反射方式破解單例的解決方法

public class Singleton {

    //私有構造方法
    private Singleton() {
        /*
           反射破解單例模式需要新增的程式碼
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //對外提供靜態方法獲取該物件
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

說明:

這種方式比較好理解。當通過反射方式呼叫構造方法進行建立建立時,直接拋異常。不執行此中操作。

JDK原始碼解析-Runtime類

Runtime類就是使用的單例設計模式。

  1. 通過原始碼檢視使用的是哪兒種單例模式
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    ...
}
  1. 使用Runtime類中的方法
public class RuntimeDemo {
    public static void main(String[] args) throws IOException {
        //獲取Runtime類物件
        Runtime runtime = Runtime.getRuntime();

        //返回 Java 虛擬機器中的記憶體總量。
        System.out.println(runtime.totalMemory());
        //返回 Java 虛擬機器試圖使用的最大記憶體量。
        System.out.println(runtime.maxMemory());

        //建立一個新的程序執行指定的字串命令,返回程序物件
        Process process = runtime.exec("ipconfig");
        //獲取命令執行後的結果,通過輸入流獲取
        InputStream inputStream = process.getInputStream();
        byte[] arr = new byte[1024 * 1024* 100];
        int b = inputStream.read(arr);
        System.out.println(new String(arr,0,b,"gbk"));
    }
}