Warm-up

Break a singleton three ways

10 minjunior18 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • The lazy null check is check-then-act, so two threads can both construct
  • A private constructor is not a boundary — setAccessible walks past it
  • Deserialization builds a new object unless readResolve says otherwise
  • The enum closes all three with no code from you

Starter

Starter.java
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

/**
 * Warm-up: a singleton has three doors, and most people guard one.
 *
 * The thread-safety question is the one everybody prepares for. Reflection
 * and serialization are the two that get asked next, and both are two lines
 * to demonstrate.
 */
public class Starter {

    /** Correct construction: lazy, thread-safe, no lock in your code. */
    static class Holder implements Serializable {
        private static class Inner { static final Holder INSTANCE = new Holder(); }
        private Holder() { }
        static Holder get() { return Inner.INSTANCE; }
    }

    enum EnumSingleton { INSTANCE }

    public static void main(String[] args) throws Exception {
        Holder a = Holder.get();
        Holder b = Holder.get();
        System.out.println("two calls to get() : same instance? " + (a == b));

        // TODO 1: break it with reflection. Get the declared constructor,
        // setAccessible(true), newInstance(). Predict the result first — most
        // people expect an exception.

        // TODO 2: break it again with serialization. Write it to a
        // ByteArrayOutputStream through an ObjectOutputStream and read it
        // back. Compare with ==.
        //
        //   Note there is no file and no framework here. Anything that
        //   serializes your objects — a cache, a session store, a queue —
        //   does exactly this.

        // TODO 3: fix the serialization case by adding to Holder:
        //
        //       private Object readResolve() { return Inner.INSTANCE; }
        //
        // Confirm it. Then say what happens if someone adds `implements
        // Serializable` to a singleton five years from now and does not know
        // this rule.

        // TODO 4: try both attacks against EnumSingleton. One throws with a
        // message worth quoting in an interview; the other simply returns the
        // same constant. Print both results.

        // TODO 5: can you protect Holder from reflection? Try throwing from
        // the constructor when the instance already exists, then say why that
        // is weaker than what the enum gives you.

        // TODO 6: name the fourth door this file does not test — the one that
        // has nothing to do with how the instance was created.
    }
}

Run it locally:

cd exercises/java/creational-patterns/thread-safe-singleton/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You produced a second instance by reflection
  • You produced a third by a serialization round trip
  • You fixed the serialization case with readResolve and confirmed it
  • You showed the enum refusing both attacks

← Back to How do you implement a thread-safe singleton?