單例與序列化的那些事兒

單例模式,是設計模式中最簡單的一種。經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。若是但願在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。關於單例模式的使用方式,能夠閱讀單例模式的七種寫法java

可是,單例模式真的可以實現實例的惟一性嗎?設計模式

答案是否認的,不少人都知道使用反射能夠破壞單例模式,除了反射之外,使用序列化與反序列化也一樣會破壞單例。測試

序列化對單例的破壞

首先來寫一個單例的類:編碼

code 1spa

1設計

2code

3對象

4接口

5ci

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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的調用棧:

readObject--->readObject0--->readOrdinaryObject--->checkResolve

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

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

1

2

3

4

5

6

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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;

    }

}

具體原理,咱們回過頭繼續分析code 3中的第二段代碼:

code 3.2

1

2

3

4

5

6

7

8

9

10

11

12

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方法,並在該方法中指定要返回的對象的生成策略,就能夠方式單例被破壞。

總結

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

相關文章
相關標籤/搜索