ExerciseWarm-up
Warm-up
Break a singleton three ways
10 minjunior1–8 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 StarterDone 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