Tuesday, May 26, 2015

Instance Control In Java Serialization

One of the behaviors of deserialization is that a new object is created every time the deserialization process is executed. This gives a big impact if we serialize singleton object, because, after deserialization, the singleton rule is no longer applied.

class App {

    private static final String EARTH_SER = "Earth.ser";
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Earth planet1 = Earth.getInstance();
        Earth planet2 = Earth.getInstance();
        System.out.println(planet1 == planet2); //true, singleton is applied
        
        serialize(Earth.getInstance());//serialize the singleton object
        
        Earth planet3 = deserialize(); //deserialize
        Earth planet4 = deserialize(); //deserialize again
        
        //false, object come back from serialization is a new/clone object
        System.out.println(planet1 == planet3); 

        //false, every object returned from serialization is a new/clone object
        System.out.println(planet3 == planet4); 
    }
    
    static void serialize(Earth instance) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(EARTH_SER))) {
            oos.writeObject(instance);
            oos.flush();
        }
    }
    
    static Earth deserialize() throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(EARTH_SER))) {
            return (Earth) ois.readObject();
        }
    }
}

class Earth implements Serializable {

    private static final Earth INSTANCE = new Earth();
    
    private Earth() {}
    
    static Earth getInstance() {
        return INSTANCE;
    }
}

Imagine if the "Earth" is an instance variable of many serializable objects, after all these objects gone through the serialization and deserialization process, each of them is actually holding a brand new "Earth" object. This not only defeats the purpose of Singleton but also causing unnecessary memory consumed by redundant "Earth" objects. In order to resolve these issues, implement the special method readResolve() in Earth class. So that, Java Serialization runtime will pick up the loaded one and only "Earth" object instead of constitutes and returns a new "Earth" object.

private Object readResolve() throws ObjectStreamException {
    return INSTANCE; //the one and only instance created when Earth class is loaded
}

Re-run the App program again, and now we will get all true result.

There is another even more straight forward and safest way to resolve the serializable singleton object issue. Since enum type was introduced in Java 5, we can make use of single-element enum type to implement the singleton pattern. It is by default guarantee to only return a single instance of its element even in serialization environment. This could free us from implementing the special method readResolve() by yourself. Replace the Earth class with the "Earth enum type" below and re-run the App, we will get all true result as well.


enum Earth {
    INSTANCE;
}

Reference:
Book: Effective Java 2nd Edition by Joshua Bloch

No comments: