設計模式(四)----創建型模式之單例模式(二)

1.1.3 存在的問題

1.1.3.1 問題演示

破壞單例模式:

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

  • 序列化反序列化

    Singleton類:

    public class Singleton implements Serializable {
    ​
        //私有構造方法
        private Singleton() {}
    ​
        //定義一個靜態內部類
        private static class SingletonHolder {
            //在內部類中聲明並初始化外部類的對象
            private static final Singleton INSTANCE = new Singleton();
        }
    ​
        //提供公共的訪問方式
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    ​
    }

    Test類:

    public class Test {
        public static void main(String[] args) throws Exception {
            //往文件中寫對象
            //writeObject2File();
            //從文件中讀取對象
            Singleton s1 = readObjectFromFile();
            Singleton s2 = readObjectFromFile();
    ​
            //判斷兩個反序列化後的對象是否是同一個對象
            System.out.println(s1 == s2);
        }
    ​
        private static Singleton readObjectFromFile() throws Exception {
            //創建對象輸入流對象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //第一個讀取Singleton對象
            Singleton instance = (Singleton) ois.readObject();
    ​
            return instance;
        }
    ​
        public static void writeObject2File() throws Exception {
            //獲取Singleton類的對象
            Singleton instance = Singleton.getInstance();
            //創建對象輸出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //將instance對象寫出到文件中
            oos.writeObject(instance);
        }
    }

    上面代碼運行結果是false,表明序列化和反序列化已經破壞了單例設計模式。

  • 反射

    Singleton類:

    public class Singleton {
    ​
        //私有構造方法
        private Singleton() {}
        
        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;
            }
        }
    }

    Test類:

    public class Test {
        public static void main(String[] args) throws Exception {
            //獲取Singleton類的字節碼對象
            Class clazz = Singleton.class;
            //獲取Singleton類的私有無參構造方法對象
            Constructor constructor = clazz.getDeclaredConstructor();
            //取消訪問檢查
            constructor.setAccessible(true);
    ​
            //創建Singleton類的對象s1
            Singleton s1 = (Singleton) constructor.newInstance();
            //創建Singleton類的對象s2
            Singleton s2 = (Singleton) constructor.newInstance();
    ​
            //判斷通過反射創建的兩個Singleton對象是否是同一個對象
            System.out.println(s1 == s2);
        }
    }

    上面代碼運行結果是false,表明反射已經破壞了單例設計模式

注意:枚舉方式不會出現這兩個問題。

1.1.3.2 問題的解決

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

    在Singleton類中添加readResolve()方法,在反序列化時被反射調用,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新new出來的對象。

    Singleton類:

    public class Singleton implements Serializable {
    ​
        //私有構造方法
        private Singleton() {}
    ​
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    ​
        //對外提供靜態方法獲取該對象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
        
        /**
         * 下面是爲了解決序列化反序列化破解單例模式
         * 當進行反序列化時,會自動調用該方法,將該方法的返回值直接返回
         */
        private Object readResolve() {
            return SingletonHolder.INSTANCE;
        }
    }

    Test類

    public class Client {
        public static void main(String[] args) throws Exception {
            //writeObject2File();
            readObjectFromFile();
            readObjectFromFile();
        }
    ​
        //從文件讀取數據(對象)
        public static void readObjectFromFile() throws Exception {
            //1,創建對象輸入流對象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //2,讀取對象
            Singleton instance = (Singleton) ois.readObject();
    ​
            System.out.println(instance);
    ​
            //釋放資源
            ois.close();
        }
    ​
        //向文件中寫數據(對象)
        public static void writeObject2File() throws Exception {
            //1,獲取Singleton對象
            Singleton instance = Singleton.getInstance();
            //2,創建對象輸出流對象
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //3,寫對象
            oos.writeObject(instance);
            //4,釋放資源
            oos.close();
        }
    }

    可以看到打印出來的兩個地址是一樣的,說明已經解決單例模式被破壞的問題

    那爲啥會自動調用readResolve這個方法呢?,實際上就是ObjectInputStream種的readObject方法在起作用

    源碼解析:

    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 static boolean flag = false;
    ​
        //私有構造方法
        private Singleton() {
            synchronized (Singleton.class) {
    ​
                //判斷flag的值是否是true,如果是true,說明非第一次訪問,直接拋一個異常,如果是false的話,說明第一次訪問
                if (flag) {
                    throw new RuntimeException("不能創建多個對象");
                }
                //將flag的值設置爲true
                flag = true;
            }
        }
    ​
        //定義一個靜態內部類
        private static class SingletonHolder {
            //在內部類中聲明並初始化外部類的對象
            private static final Singleton INSTANCE = new Singleton();
        }
    ​
        //提供公共的訪問方式
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    ​

說明:

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

接下來介紹一個在jdk中源碼中使用單例模式的一個類

1.1.4 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() {}
        ...
    }

    從上面源代碼中可以看出Runtime類使用的是餓漢式(靜態屬性)方式來實現單例模式的。

  2. 使用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"));
        }
    }

執行結果如下

  

 

 

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