天道不一定酬所有勤
但是,天道只酬勤

体彩天津11选5走势图表:單例與序列化的那些事兒

開發十年,就只剩下這套架構體系了??!

天津11选5蛋托玩法 www.ijudhr.com.cn 本文將通過實例+閱讀Java源碼的方式介紹序列化是如何破壞單例模式的,以及如何避免序列化對單例的破壞。

單例模式,是設計模式中最簡單的一種。通過單例模式可以保證系統中一個類只有一個實例而且該實例易于外界訪問,從而方便對實例個數的控制并節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。關于單例模式的使用方式,可以閱讀單例模式的七種寫法

但是,單例模式真的能夠實現實例的唯一性嗎?

答案是否定的,很多人都知道使用反射可以破壞單例模式,除了反射以外,使用序列化與反序列化也同樣會破壞單例。

序列化對單例的破壞

首先來寫一個單例的類:

code 1

package com.hollis;
import java.io.Serializable;
/**
 * Created by hollis on 16/2/5.
 * 使用雙重校驗鎖方式實現單例
 */
public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

接下來是一個測試類:

code 2

package com.hollis;
import java.io.*;
/**
 * Created by hollis on 16/2/5.
 */
public class SerializableDemo1 {
    //為了便于理解,忽略關閉流操作及刪除文件操作。真正編碼時千萬不要忘記
    //Exception直接拋出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        //判斷是否是同一個對象
        System.out.println(newInstance == Singleton.getSingleton());
    }
}
//false

輸出結構為false,說明:

通過對Singleton的序列化與反序列化得到的對象是一個新的對象,這就破壞了Singleton的單例性。

這里,在介紹如何解決這個問題之前,我們先來深入分析一下,為什么會這樣?在反序列化的過程中到底發生了什么。

ObjectInputStream

對象的序列化過程通過ObjectOutputStream和ObjectInputputStream來實現的,那么帶著剛剛的問題,分析一下ObjectInputputStream 的readObject 方法執行情況到底是怎樣的。

為了節省篇幅,這里給出ObjectInputStream的readObject的調用棧:

這里看一下重點代碼,readOrdinaryObject方法的代碼片段: code 3

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        //此處省略部分代碼

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

        //此處省略部分代碼

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

code 3 中主要貼出兩部分代碼。先分析第一部分:

code 3.1

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

這里創建的這個obj對象,就是本方法要返回的對象,也可以暫時理解為是ObjectInputStream的readObject返回的對象。

isInstantiable:如果一個serializable/externalizable的類可以在運行時被實例化,那么該方法就返回true。針對serializable和externalizable我會在其他文章中介紹。

desc.newInstance:該方法通過反射的方式調用無參構造方法新建一個對象。

所以。到目前為止,也就可以解釋,為什么序列化可以破壞單例了?

答:序列化會通過反射調用無參數的構造方法創建一個新的對象。

那么,接下來我們再看剛開始留下的問題,如何防止序列化/反序列化破壞單例模式。

防止序列化破壞單例模式

先給出解決方案,然后再具體分析原理:

只要在Singleton類中定義readResolve就可以解決該問題:

code 4

package com.hollis;
import java.io.Serializable;
/**
 * Created by hollis on 16/2/5.
 * 使用雙重校驗鎖方式實現單例
 */
public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Object readResolve() {
        return singleton;
    }
}

還是運行以下測試類:

package com.hollis;
import java.io.*;
/**
 * Created by hollis on 16/2/5.
 */
public class SerializableDemo1 {
    //為了便于理解,忽略關閉流操作及刪除文件操作。真正編碼時千萬不要忘記
    //Exception直接拋出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        //判斷是否是同一個對象
        System.out.println(newInstance == Singleton.getSingleton());
    }
}
//true

本次輸出結果為true。具體原理,我們回過頭繼續分析code 3中的第二段代碼:

code 3.2

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

hasReadResolveMethod:如果實現了serializable 或者 externalizable接口的類中包含readResolve則返回true

invokeReadResolve:通過反射的方式調用要被反序列化的類的readResolve方法。

所以,原理也就清楚了,主要在Singleton中定義readResolve方法,并在該方法中指定要返回的對象的生成策略,就可以防止單例被破壞。

總結

在涉及到序列化的場景時,要格外注意他對單例的破壞。

推薦閱讀

深入分析Java的序列化與反序列化

(全文完) 歡迎關注『Java之道』微信公眾號
贊(8)
如未加特殊說明,此網站文章均為原創,轉載必須注明出處。天津11选5蛋托玩法 » 單例與序列化的那些事兒
分享到: 更多 (0)

評論 9

  • 昵稱 (必填)
  • 郵箱 (必填)
  • 網址
  1. #1

    honey,how are you

    3566884年前 (2016-02-07)回復
  2. #2

    It’s not bad

    3566884年前 (2016-02-12)回復
  3. #3

    code 4不合理吧,最后一行應該是return getInstance();

    TemporaryofCP4年前 (2016-06-12)回復
    • 我也發現了。。頂你上去

      hang2df2年前 (2018-06-20)回復
  4. #4

    什么情景下會出現單例被序列化后又反序列化出來呢?

    博予liutxer3年前 (2016-10-23)回復
  5. #5

    序列化會通過反射調用無參數的構造方法創建一個新的對象。

    這句話有點不理解。如果沒有無參的構造方法呢,一樣可以反序列化啊~求解釋

    浮生若夢2年前 (2017-09-12)回復
    • 默認無參的

      只是誤會一場2年前 (2018-01-29)回復
    • 這里作者的確沒有說清楚。首先如果是Externalizable類,你必須有無參構造方法,用來反序列化時通過反射創建對象!如果是Serializable類,只要該類的第一個不可序列化的父類定義了一個可訪問的無參數構造方法,那么就能創建對象,即此時反序列化用的是該父類的無參構造函數來創建對象的,跟當前目標對象有木有無參構造方法沒關系。
      看看code3代碼中的 obj = desc.isInstantiable() ? desc.newInstance() : null;這行代碼,讀一下isInstantiable方法的JavaDoc文檔就好。

      拿筆小星1年前 (2019-02-20)回復
  6. #6

    你好,我的例子中,反序列化獲得目標對象,為什么沒有調用無參構造函數呢?好奇怪,程序也成功了。

    偏愛他2年前 (2018-06-28)回復

HollisChuang's Blog

聯系我關于我