代码之家  ›  专栏  ›  技术社区  ›  frictionlesspulley sameer

如何在序列化的同时处理单例

  •  36
  • frictionlesspulley sameer  · 技术社区  · 14 年前

    假设我有一个单例类,定义如下。

    public class MySingleton implements Serializable{
     private static MySingleton myInstance;
    
     private MySingleton(){
    
     }
      static{
        myInstance =new MySingleton();
     }
     public static MySingleton getInstance(){
        return MySingleton.myInstance;
     }
    }
    

    上面根据me定义的定义满足了单例的要求,唯一增加的行为是类实现了可序列化的接口。

    如果另一个类x获取了single的实例并将其写入一个文件,然后在稍后将其反序列化以获取另一个实例,那么我们将拥有两个违反singleton原则的实例。

    我怎样才能避免这种情况,或者我在上面的定义本身是错误的。

    9 回复  |  直到 6 年前
        1
  •  35
  •   ColinD    14 年前

    最好的方法是使用枚举单例模式:

    public enum MySingleton {
      INSTANCE;
    }
    

    这保证了对象的单一性,并为您提供可序列化性,使您始终获得相同的实例。

    一般来说,您可以提供 readResolve() 方法如下:

    protected Object readResolve() {
      return myInstance;
    }
    
        2
  •  27
  •   Stephen C    11 年前

    @科林德是对的,但他的回答也说明了为什么单子并没有真正的系列化。

    下面是序列化枚举值时发生的情况(请参见 here )

    序列化枚举实例的规则与序列化“普通”可序列化对象的规则不同:枚举实例的序列化形式仅包含其枚举常量名称以及标识其基本枚举类型的信息。反序列化行为也不同——类信息用于查找适当的枚举类,并使用该类和接收到的常量名称调用Enum.ValueOf方法,以获取要返回的枚举常量。

    因此,附加到枚举值的任何其他状态都不会在序列化和反序列化之后继续存在。

    通过将自定义序列化/反序列化代码添加到singleton类中,您可以自己做同样的事情。该代码要么根本不记录单例的状态,要么在反序列化单例时丢弃它。不管怎样,你都要把逻辑放到 readResolve() 方法由@colind的答案解释。

    现在,我假定您想要序列化单例的原因是您想要保持它们的状态。不幸的是,这提出了一个概念上的问题。假设您的应用程序已经在正常的事件过程中实例化了singleton,然后它反序列化了一些对象图,其中包括singleton的前一个实例的副本。它能做什么?

    • 如果它正常反序列化单例,那么它违反了“单例性”。
    • 如果没有,那么应用程序就无法访问singleton以前的状态。
        3
  •  5
  •   iirekm    14 年前

    使用Enum的解决方案不能与由Spring、EJB、Guice或任何其他DI框架管理的单例程序一起工作。它只适用于枚举,因为枚举是由序列化算法专门处理的。

    首先, 单例不需要序列化 ,因为如果反序列化它,然后反序列化单例!=yoursingleton.getInstance(),这意味着您的singleton有两个实例,这意味着您的singleton根本不是singleton,这可能导致不可预知的错误。

    但是,有时需要序列化包含对singleton引用的非singleton。解决方案很简单:

    class NonSingleton implements Serializable {
        private transient YourSingleton singleton = YourSingleton.getInstance();
        ...
    }
    

    带弹簧:

    @Configurable
    class NonSingleton implements Serializable {
        @Autowired
        private transient YourSingleton singleton;
        ...
    }
    
        4
  •  4
  •   Ilya    8 年前

    下面是我的 Singleton 实现的类 Serializable 接口。标记它包含 readResolve() 方法也。

    import java.io.Serializable;
    
    public class Singleton implements Serializable {
    
        private static Singleton singleton = new Singleton( );
    
        public int i = 1;
    
        private Singleton() { }
    
        public static Singleton getInstance( ) {
    
           return singleton;
        }
    
        public Object readResolve() {
           return getInstance( );
        }
    
        public static void main(String[] args) {
            Singleton s1 = getInstance();
            System.out.println(s1.hashCode());
    
            Singleton s2 = getInstance();
            System.out.println(s2.hashCode());
        }
    }
    

    下面是将首先序列化然后反序列化上述类的类。这里反序列化发生了两次,但由于readresolve()方法,两次都只创建一个实例。

    public class SingletonSerializableDemo {
    
        static Singleton sing = Singleton.getInstance();
        static Singleton s1  = null;
        static Singleton s2 = null;
        public static void main(String[] args) {
            try {
                 FileOutputStream fileOut =
                 new FileOutputStream("E:/singleton.ser");
                 ObjectOutputStream out = new ObjectOutputStream(fileOut);
                 out.writeObject(sing);
                 out.close();
                 fileOut.close();
                 System.out.println("Serialized data is saved");
    
                 FileInputStream fileIn1 = new FileInputStream("E:/singleton.ser");
                 FileInputStream fileIn2 = new FileInputStream("E:/singleton.ser");
                 ObjectInputStream in1 = new ObjectInputStream(fileIn1);
                 ObjectInputStream in2 = new ObjectInputStream(fileIn2);
                 s1 = (Singleton) in1.readObject();
                 s2 = (Singleton) in2.readObject();
                 System.out.println(s1.hashCode() + " "+ s1.i);
                 s1.i = 10;
                 System.out.println(s2.hashCode() + " "+ s2.i);
                 in1.close();
                 in2.close();
                 fileIn1.close();
                 fileIn2.close();
              }catch(Exception i) {
                 i.printStackTrace();
              }
        }
    }
    

    输出为:

    已保存序列化数据
    21061094 1
    21061094 10

    结论: singleton类也可以通过保留 读解析() Singleton类中的方法。

        5
  •  1
  •   Rajesh    7 年前

    这可能是一个熟悉的解决方案,但以防万一。

    public class ConnectionFactory implements Serializable {
    
        //Static variable for holding singleton reference object
        private static ConnectionFactory INSTANCE;
    
        /**
         * Private constructor
         */
        private ConnectionFactory() {
    
        }
    
        /**
         * Static method for fetching the instance
         *
         * @return
         */
        public static ConnectionFactory getIntance() {
            //Check whether instance is null or not
            if (INSTANCE == null) {
                //Locking the class object
                synchronized (ConnectionFactory.class) {
                    //Doing double check for the instance
                    //This is required in case first time two threads simultaneously invoke
                    //getInstance().So when another thread get the lock,it should not create the
                    //object again as its already created by the previous thread.
                    if (INSTANCE == null) {
                        INSTANCE = new ConnectionFactory();
                    }
                }
            }
            return INSTANCE;
        }
    
        /**
         * Special hook provided by serialization where developer can control what object needs to sent.
         * However this method is invoked on the new object instance created by de serialization process.
         *
         * @return
         * @throws ObjectStreamException
         */
        private Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    
    }
    

    测试代码

    public class SerializationTest {
    
        public static void main(String[] args) {
            ConnectionFactory INSTANCE = ConnectionFactory.getIntance();
    
            try {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connectFactory.ser"));
                oos.writeObject(INSTANCE);
                oos.close();
    
                ObjectInputStream osi = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
                ConnectionFactory factory1 = (ConnectionFactory) osi.readObject();
                osi.close();
    
                ObjectInputStream osi2 = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
                ConnectionFactory factory2 = (ConnectionFactory) osi2.readObject();
                osi2.close();
    
    
                System.out.println("Instance reference check->" + factory1.getIntance());
                System.out.println("Instance reference check->" + factory2.getIntance());
                System.out.println("===================================================");
                System.out.println("Object reference check->" + factory1);
                System.out.println("Object reference check->" + factory2);
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    
    }
    

    产量

    Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
    Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
    ===================================================
    Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
    Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
    
        6
  •  1
  •   glennsl Namudon'tdie    7 年前

    假设我们有以下单例类:

    public class ConnectionFactory implements Serializable {
        private static ConnectionFactory INSTANCE;
    
        private ConnectionFactory() {  }
    
        public static ConnectionFactory getInstance() {
            if (INSTANCE == null) {
                synchronized(ConnectionFactory.class) {
                    if(INSTANCE == null)
                        INSTANCE = new ConnectionFactory();
                }
            }
            return INSTANCE;
        }
    }
    

    现在我们有了下面这样的主要类来序列化和反序列化对象:

     public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
         ConnectionFactory INSTANCE=ConnectionFactory.getInstance();
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connFactory.ser"));  
         oos.writeObject(INSTANCE);  
         oos.close();
    
         // Here I am recreating the instance by reading the serialized object data store
         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("connFactory.ser"));  
         ConnectionFactory factory1 = (ConnectionFactory) ois.readObject();  
         ois.close();  
    
         // I am recreating the instance AGAIN by reading the serialized object data store
         ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("connFactory.ser"));  
         ConnectionFactory factory2 = (ConnectionFactory) ois2.readObject();  
         ois2.close();
    
         // Let's see how we have broken the singleton behavior
         System.out.println("Instance reference check->" +factory1.getInstance());
         System.out.println("Instance reference check->" +factory2.getInstance());
         System.out.println("=========================================================");
         System.out.println("Object reference check->" + factory1);
         System.out.println("Object reference check->" + factory2);
    }
    

    因此,如果我们执行上述代码,我们将得到以下行为: “例如,它创建了两个对象和一个静态引用。这意味着,如果我们多次读取单个对象的序列化格式,我们将创建多个对象。这不是单例对象应该做的。那么我们能避开我吗?是的,我们可以。”

    为了避免singleton类的多个实例,我们将使用序列化提供的以下方法:

    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
    

    这将阻止创建单个类的多个实例。

        7
  •  0
  •   Akshay Lokur    10 年前

    我认为单例可以序列化,下面是关于如何进行的代码:

    
    import java.io.Serializable;
    
    public class MySingleton implements Serializable {
    
        private MySingleton(String name) {
            this.name = name;
        }
    
        private static MySingleton mySingleton;
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static MySingleton getInstance(String name) {
            if(mySingleton == null) {
                System.out.println("in if...");
                mySingleton = new MySingleton(name);
            }
    
            return mySingleton;
        }
    }
    

    下面是“main”方法,它获取上面的singleton类的实例,对其进行序列化和反序列化:

    
    
     public static void main (String[] args) {
    
            MySingleton m = MySingleton.getInstance("Akshay");
    
            try {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://temp.ser"));
                oos.writeObject(m);
    
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://temp.ser"));
                MySingleton m2 = (MySingleton) ois.readObject();
                System.out.println(m2.getName());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    

    输出是:

    如果…

    阿克舍

    谢谢。

        8
  •  0
  •   Abhinav Katyayen    6 年前

    下面是打破singleton类的答案,以及如何使用readresolve()方法防止类创建不同的对象;

    导入java.io.serializable;

    公共类singleton实现可序列化{

    private static final long serialVersionUID = 1L;
    
    private Singleton() {
    }
    
    private static class SingletonHelper {
    
        private static final Singleton INSTANCE = new Singleton();
    
    }
    
    public static Singleton getInstance() {
    
        return SingletonHelper.INSTANCE;
    }
    
    private Object readResolve() {
        Singleton instance = getInstance();
        return instance;
    }
    

    }

    公共类breaksingletonusingserialization{

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    
        Singleton demo1 =Singleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream("C:/Eclipse/serial.ser"));
        out.writeObject(demo1);
        Singleton demo2 =null;
        ObjectInput in = new ObjectInputStream(new FileInputStream("C:/Eclipse/serial.ser"));
    
        demo2 = (Singleton)in.readObject();
    
        System.out.println("Hascode demo1 : " +demo1);
        System.out.println("Hascode demo2 : " +demo2);
    }
    

    }

        9
  •  -1
  •   Sumit Singh phluid    12 年前

    单例类就像一个管理器或控制器,一般来说,我们不希望保存任何控制器的状态,而不是实体的状态。一般来说,我们需要保存任何实体的对象状态,而不是控制器。
    对于单个类加载器,singleton是singleton,而不是多个类加载器。如果一个类装载在一个类装载器中,那么另一个类装载器将不知道它,所以它的行为是这样的。