Challenge

Five singletons, rank them

20 minintermediate110 yrs

Edge cases. You have to reason, and two valid fixes differ.

What this teaches

  • Each form differs on laziness, locking, reflection and serialization
  • Double-checked locking without volatile is broken, not merely risky
  • Only the enum closes all three doors with no code from you
  • A correctly constructed singleton can still hold shared mutable state

Starter

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

/**
 * Challenge: five singletons. Score them, then rank them.
 *
 * Four axes: lazy, thread-safe, survives reflection, survives serialization.
 * Fill the table in before writing any code — three of the five can be
 * decided by reading, and one of the defects cannot be demonstrated by
 * running it at all.
 */
public class Starter {

    /* ─────────── A ─────────── */
    static class Eager implements Serializable {
        private static final Eager INSTANCE = new Eager();
        private Eager() { }
        static Eager get() { return INSTANCE; }
    }

    /* ─────────── B ─────────── */
    static class SynchronizedAccessor implements Serializable {
        private static SynchronizedAccessor instance;
        private SynchronizedAccessor() { }
        static synchronized SynchronizedAccessor get() {
            if (instance == null) instance = new SynchronizedAccessor();
            return instance;
        }
    }

    /* ─────────── C ─────────── */
    static class DoubleChecked implements Serializable {
        private static DoubleChecked instance;          // note what is missing
        private DoubleChecked() { }
        static DoubleChecked get() {
            if (instance == null) {
                synchronized (DoubleChecked.class) {
                    if (instance == null) instance = new DoubleChecked();
                }
            }
            return instance;
        }
    }

    /* ─────────── D ─────────── */
    static class HolderClass implements Serializable {
        private static class Inner { static final HolderClass INSTANCE = new HolderClass(); }
        private HolderClass() { }
        static HolderClass get() { return Inner.INSTANCE; }
        private Object readResolve() { return Inner.INSTANCE; }
    }

    /* ─────────── E ─────────── */
    enum EnumSingleton {
        INSTANCE;
        public final Map<String, String> cache = new HashMap<>();
    }

    public static void main(String[] args) throws Exception {
        System.out.println("score these before running anything:");
        System.out.println();
        System.out.println("                       lazy   safe   reflection   serialization");
        System.out.println("  A Eager              ____   ____   __________   _____________");
        System.out.println("  B Synchronized       ____   ____   __________   _____________");
        System.out.println("  C DoubleChecked      ____   ____   __________   _____________");
        System.out.println("  D HolderClass        ____   ____   __________   _____________");
        System.out.println("  E EnumSingleton      ____   ____   __________   _____________");

        // TODO 1: fill the table in. Three rows can be settled by reading.

        // TODO 2: C has a defect that a test cannot expose — it may work for
        // years on x86 and fail on ARM. Name it, name the one word that fixes
        // it, and say why "I ran it and it was fine" is not evidence.

        // TODO 3: write the reflection attack once and run it against all
        // five. Four fall over. Print the results as a table.

        // TODO 4: write the serialization round trip and run it against all
        // five. Note which one needed a line of code to survive, and which one
        // needed nothing.

        // TODO 5: rank the five for a new codebase and defend the top two in
        // two sentences.

        // TODO 6: E scores perfectly on all four axes and is still wrong.
        // Find the reason — it is on the line after INSTANCE — and say why
        // this is the mistake the pattern discussion always hides.

        // TODO 7: in a Spring application, which of these five would you
        // actually write? Answer honestly.
    }
}

Run it locally:

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

Hints

  1. Hint 1

    Score each on four axes before judging: lazy, thread-safe, reflection, serialization.

  2. Hint 2

    One of the five has a defect you cannot demonstrate by running it. Say which and why that makes it worse, not better.

  3. Hint 3

    Two of the five are equally correct on construction. What separates them is what happens after.

  4. Hint 4

    One is correct on all four axes and still wrong for a different reason. Look at what it exposes.

Done when

  • All five are scored on all four axes, in a table
  • The broken double-checked locking is identified and fixed with one word
  • You ranked them and can defend the top two
  • The one with correct construction and unsafe state is named

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