Java 序列化的问题之二——单例

第二个问题是如何读取应该是单例(Singleton)的序列化数据?当然要保证一是单例,二是数据正确性。Google 出来的所有文章都是一个调:private Object readResolve() { return getInstance(); }。方法没错,肯定要使用 readResolve() 返回一个唯一的实例。但真的可行吗?看下面的程序:

  1. WrongSingletonObject.java
  2.  
  3. public class WrongSingletonObject implements Serializable {
  4.     private static WrongSingletonObject instance;
  5.  
  6.     private String value = "value1";
  7.  
  8.     private WrongSingletonObject() {
  9.     }
  10.  
  11.     public static synchronized WrongSingletonObject getInstance() {
  12.         if (instance == null) {
  13.             instance = new WrongSingletonObject();
  14.         }
  15.         return instance;
  16.     }
  17.  
  18.     public void setValue(String value) {
  19.         this.value = value;
  20.     }
  21.  
  22.     public String getValue() {
  23.         return value;
  24.     }
  25.  
  26.     private Object readResolve() throws ObjectStreamException {
  27.         return getInstance();
  28.     }
  29. }
  30.  
  31.     public void singletonWrong(boolean save) {
  32.         String file = "build/singleton-wrong.ser";
  33.         if (save) {
  34.             try {
  35.                 ObjectOutputStream out = createOutputStream(false, file);
  36.                 WrongSingletonObject singleton = WrongSingletonObject.getInstance();
  37.                 System.out.println("Original Singleton Value is " + singleton.getValue());
  38.                 singleton.setValue("value2");
  39.                 System.out.println("New Singleton Value is " + singleton.getValue());
  40.  
  41.                 out.writeObject(singleton);
  42.  
  43.                 out.close();
  44.             } catch (Exception e) {
  45.                 e.printStackTrace();
  46.             }
  47.         } else {
  48.             try {
  49.                 ObjectInputStream in = createInputStream(false, file);
  50.  
  51.                 WrongSingletonObject singleton = (WrongSingletonObject) in.readObject();
  52.                 System.out.println("Loaded Singleton Value is " + singleton.getValue());
  53.                 System.out.println("Singleton.getInstance() value is "
  54.                                    + WrongSingletonObject.getInstance().getValue());
  55.  
  56.                 in.close();
  57.             } catch (Exception e) {
  58.                 e.printStackTrace();
  59.             }
  60.         }
  61.     }

老规矩,使用 ant run-singleton-wrong 来运行,结果会是什么呢?

  1. run-class:
  2.      [echo] === Singleton: Wrong, Saving ===
  3.      [java] Original Singleton Value is value1
  4.      [java] New Singleton Value is value2
  5.  
  6. run-class:
  7.      [echo] === Singleton: Wrong, Loading ===
  8.      [java] Loaded Singleton Value is value1
  9.      [java] Singleton.getInstance() value is value1

我们在序列化之前将对象中的值设为 "value2",但逆序列化之后得到的值却是缺省的 "value1"。问题出在哪里呢?答案其实很简单,在 readResolve() 方法中,直接用 getInstance() 创建出一个新的对象并将其返回。那其中的值理所当然就是原来的 "value1"。

那又该如何解决?就是要把唯一的这个 instance 不是 new 出来,而是要直接赋值为逆序列化出来的对象。那这个逆序列化出来的对象在哪里呢?其实是“不识庐山真面目,只缘身在此山中”,如下:

  1. SingletonObject.java
  2.  
  3.     private Object readResolve() throws ObjectStreamException {
  4.         instance = this;
  5.         return getInstance();
  6.     }

逆序列化时调用的 readResolve()就是针对你序列化的对象本身的,也就是中其中的 this 就是我们想要的对象。就是[Java Serialization Specification] 逆序列化的第 12 步。结果如下:

  1. run-class:
  2.      [echo] === Singleton: Correct, Saving ===
  3.      [java] Original Singleton Value is value1
  4.      [java] New Singleton Value is value2
  5.  
  6. run-class:
  7.      [echo] === Singleton: Correct, Loading ===
  8.      [java] Loaded Singleton Value is value2
  9.      [java] Singleton.getInstance() value is value2

这样,问题就解决了,跟我们预期的一样。

最后,其实还有一个重要的问题,就是对单例类逆序列化的时机。如果别的类在单例类逆序列化之前就得到了单例类的实例,并保留了一份引用,这样整个系统中就有两个实例,而非一个,就破坏了单例的意义。所以应该在任何类使用之前对其进行逆序列化。不过这已经属于逻辑问题,不属于技术问题。

所有的代码可以下载:serialization.tar.gz

你这篇文章不对呀,我试了下,没有加这句“instance

你这篇文章不对呀,我试了下,没有加这句“instance = this;“,结果是正确的呀,你的错了吧,怎么会有第一种结果出来?

你这篇文章不对呀,我试了下,没有加这句“instance

你这篇文章不对呀,我试了下,没有加这句“instance = this;“,结果是正确的呀,你的错了吧,怎么会有第一种结果出来?

你这篇错误!根本不需要加” instance =

你这篇错误!根本不需要加” instance = this;“,如下代码你自己试试

  1. package com.vista;
  2.  
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.io.ObjectStreamException;
  11. import java.io.Serializable;
  12.  
  13. class WrongSingletonObject implements Serializable
  14. {
  15.     private static WrongSingletonObject instance = null;
  16.  
  17.     private String value = "value1";
  18.  
  19.     private WrongSingletonObject()
  20.         {
  21.     }
  22.  
  23.     public static synchronized WrongSingletonObject getInstance()
  24.         {
  25.         if (instance == null)
  26.                 {
  27.             instance = new WrongSingletonObject();
  28.         }
  29.         return instance;
  30.     }
  31.  
  32.     public void setValue(String value)
  33.         {
  34.         this.value = value;
  35.     }
  36.  
  37.     public String getValue()
  38.         {
  39.         return value;
  40.     }
  41.  
  42.     private Object readResolve() throws ObjectStreamException
  43.         {
  44.         //instance = this;
  45.         return getInstance();
  46.     }
  47.  
  48. }
  49.  
  50.  
  51. public class Hello
  52. {
  53.         public ObjectOutputStream createOutputStream(String file,boolean bAppend)
  54.         {
  55.                 File testFile=new File(file);
  56.                 ObjectOutputStream out = null;
  57.                 try
  58.                 {
  59.                         out = new ObjectOutputStream(new FileOutputStream(testFile,bAppend));
  60.                 }
  61.                 catch (FileNotFoundException e)
  62.                 {
  63.                         e.printStackTrace();
  64.                 }
  65.                 catch (IOException e)
  66.                 {
  67.                         e.printStackTrace();
  68.                 }
  69.                 return out;
  70.  
  71.         }
  72.         public ObjectInputStream createInputStream(String file,boolean bAppend)
  73.         {
  74.                 File testFile=new File(file);
  75.                 ObjectInputStream out = null;
  76.                 try
  77.                 {
  78.                         out = new ObjectInputStream(new FileInputStream(testFile));
  79.                 }
  80.                 catch (FileNotFoundException e)
  81.                 {
  82.                         e.printStackTrace();
  83.                 }
  84.                 catch (IOException e)
  85.                 {
  86.                         e.printStackTrace();
  87.                 }
  88.                 return out;
  89.  
  90.         }
  91.     public void singletonWrong(boolean save)
  92.     {
  93.         String file = "C:\\singleton-wrong.data";
  94.         if (save)
  95.         {
  96.             try
  97.             {
  98.                 ObjectOutputStream out = createOutputStream(file,false);
  99.                 WrongSingletonObject singleton = WrongSingletonObject.getInstance();
  100.                 System.out.println("Original Singleton Value is " + singleton.getValue());
  101.                 singleton.setValue("value2");
  102.                 System.out.println("New Singleton Value is " + singleton.getValue());
  103.  
  104.                 out.writeObject(singleton);
  105.  
  106.                 out.close();
  107.             }
  108.             catch (Exception e)
  109.             {
  110.                 e.printStackTrace();
  111.             }
  112.         }
  113.         else
  114.         {
  115.             try
  116.             {
  117.                 ObjectInputStream in = createInputStream(file,false);
  118.  
  119.                 WrongSingletonObject singleton = (WrongSingletonObject) in.readObject();
  120.                 System.out.println("Loaded Singleton Value is " + singleton.getValue());
  121.                 System.out.println("Singleton.getInstance() value is "
  122.                                    + WrongSingletonObject.getInstance().getValue());
  123.  
  124.                 in.close();
  125.             }
  126.             catch (Exception e)
  127.             {
  128.                 e.printStackTrace();
  129.             }
  130.         }
  131.     }
  132.         /**
  133.          * @param args
  134.          */
  135.         public static void main(String[] args)
  136.         {
  137.                 // TODO Auto-generated method stub
  138.                 Hello hello = new Hello();
  139.                 hello.singletonWrong(true);
  140.                 hello.singletonWrong(false);
  141.  
  142.         }
  143.  
  144. }
  145. [code]

你的错误原因

  1.         public static void main(String[] args)
  2.         {
  3.                 // TODO Auto-generated method stub
  4.                 Hello hello = new Hello();
  5.                 hello.singletonWrong(true);
  6.                 hello.singletonWrong(false);
  7.         }

你的错误原因就在于你在同一个 JVM 实例中进行序列化与反序列化,SingletonObject 本来指向的就是同一个,当然结果是一样的。

如果你将上面 hello.singletonWrong(XXX); 中的 XXX 分两次设置为 truefalse,并分两次运行,你就知道结果了。

或者更简单一点,程序拷贝两份放在不同的目录中,一个设置为 true,另一个设置为 false,先运行第一个,再运行第二个,再看看结果如何。

不加这一句照样是正确的,你怎么解释?

不加这一句照样是正确的,你怎么解释?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
  • You can use BBCode tags in the text.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
       __        __           ___    _   _  __   __  _____   ___ 
____ \ \ / / _ _ ( _ ) | \ | | \ \ / / |___ / |_ _|
|_ / \ \ /\ / / | | | | / _ \ | \| | \ V / |_ \ | |
/ / \ V V / | |_| | | (_) | | |\ | | | ___) | | |
/___| \_/\_/ \__,_| \___/ |_| \_| |_| |____/ |___|
Enter the code depicted in ASCII art style.