單例、序列化和readResolve()方法

說到這個話題,我先拋出單例的餓漢式寫法安全

單例:餓漢式

public class HungrySingleton  {
    private HungrySingleton() {
    }

    private static final HungrySingleton hungry = new HungrySingleton();

    public static HungrySingleton getInstance() {
        return hungry;
    }
}

首先需讓HungrySingleton支持序列化, 修改HungrySingleton類函數

public class HungrySingleton implements Serializable {

寫一個 測試類對該餓漢式進行序列化、反序列化測試

public class Client {
    public static void main(String[] args) {
        HungrySingleton s1 = HungrySingleton.getInstance();
        HungrySingleton s2 = null;
        try {
            // 將s1序列化到磁盤
            FileOutputStream fos = new FileOutputStream("a.obj");
            @Cleanup
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s1);

            oos.flush();

            @Cleanup
            FileInputStream fis = new FileInputStream("a.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            // 從磁盤反序列化
            s2 = (HungrySingleton) ois.readObject();
            System.out.println(s1 == s2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

運行結果
在這裏插入圖片描述ui

從運行結果能夠看出,序列化破壞了單例,產生了多個實例。
那咱們如何解決呢?
在HungrySingleton 類中添加 readResolve()方法就能夠完美解決spa

public class HungrySingleton implements Serializable {
    private HungrySingleton() {
    }

    private static final HungrySingleton hungry = new HungrySingleton();

    public static HungrySingleton getInstance() {
        return hungry;
    }
    // 咱們添加的readResolve()方法
    private Object readResolve() {
        return hungry;
    }
}

如上添加後,運行Client咱們對測試類,能夠看到打印出true。
你們必定會有疑問,readResolve這個方法爲啥能夠解決序列化破壞單例的問題3d

readResolve()爲啥就能夠解決序列化破壞單例的問題呢?

源頭就在於咱們測試類中的code

ObjectInputStream ois = new ObjectInputStream(fis);
  singleton1 = (HungrySingleton) ois.readObject();// 這句代碼是咱們的入手點

進到ObjectInputStream#readObject()看源碼,try第一句代碼就是對象

//返回的obj對象,就是ObjectInputStream的readObject0返回的對象。
  Object obj = readObject0(false);

進入ObjectInputStream#readObject0(),switch語句對枚舉或者Object類都有對應的序列化機制
在這裏插入圖片描述
重點代碼blog

case TC_ENUM:
// 這句代碼是針對枚舉,單例中爲啥枚舉式最安全,就是看這行代碼,後續,小夥伴能夠研讀研讀
                    return checkResolve(readEnum(unshared));

 case TC_OBJECT:
 //咱們的Object 類
                    return checkResolve(readOrdinaryObject(unshared));

checkResolve:檢查對象,並替換
readOrdinaryObject:讀取二進制對象
咱們先進入readOrdinaryObject()方法圖片

try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

能夠看到,readOrdinaryObject()方法是經過desc.isInstantiable() 來判斷是否須要new一個對象,若是返回true,方法經過反射的方式調用無參構造方法新建一個對象,不然,返回空。
那咱們進入isInstantiable()方法,

boolean isInstantiable() {
        requireInitialized();
        //cons是構造函數
        return (cons != null);
    }

cons != null是判斷類的構造方法是否爲空,咱們你們應該知道,Class類的構造方法確定不爲空,顯然isInstantiable()返回true,也就是說,必定會new 一個對象,且被obj接收。

咱們回到readOrdinaryObject()方法,查看初始化完成後的操做。
resolve.png

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            // !敲重點,這句代碼塊是爲何會執行咱們在單例中定義的readResolve()方法的核心。

            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

咱們要敲黑板了,這裏就是單例類中定義readResolve就能夠解決問題的關鍵所在!
若"obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()"這條語句返回true
就會調用Object rep = desc.invokeReadResolve(obj) 這條語句。

咱們進入hasReadResolveMethod()方法

boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

"readResolveMethod != null "的判斷,咱們深究進去,readResolveMethod是從哪裏讀取進來的

/** class-defined readResolve method, or null if none */
  // 定義readResolveMethod 的方法

    private Method readResolveMethod;

// 對readResolveMethod賦值, 經過反射得到類中名爲readResolve的方法
 readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);

在這裏插入圖片描述
resolve2.png

也就是說!
若目標類有readResolve方法,那就經過反射的方式調用要被反序列化的類中的readResolve方法,返回一個對象,而後把這個新的對象複製給以前建立的obj(即最終返回的對象)。那被反序列化的類中的readResolve 方法裏是什麼?就是直接返回咱們的單例對象。
再次貼上來,咱們的單例類。

public class HungrySingleton implements Serializable {
    private HungrySingleton() {
    }

    private static final HungrySingleton hungry = new HungrySingleton();

    public static HungrySingleton getInstance() {
        return hungry;
    }
    private Object readResolve() {
        return hungry;
    }
}

嘻嘻,若有不對之處還但願各位留言指正,以避免誤導他人。

相關文章
相關標籤/搜索