Safe publication and lazy init

Multithreading · lesson 33 of 38 · 4 min read

How a reference becomes visible before the object it points to is finished, and the safe fixes.

Open this lesson in the learning hub

Key points

  • Publishing means letting another thread see a reference. Without a barrier it can see the reference before the constructor finished.
  • The classic bug is double-checked locking on a non-volatile field: the reference lands before the fields it points at do.
  • Safe ways to publish: a static final field, a volatile field, a lock, or a concurrent collection.
  • The holder idiom is the cleanest lazy singleton: class initialisation is lazy and thread-safe, and the JVM does the locking.
  • Never let this escape its constructor, for instance by registering a listener before the object is complete.

Example

public class Main {
    // 1. The holder idiom: lazy, thread-safe, and the JVM does the locking for you.
    static final class Holder {
        static final int[] DATA = { 1, 2, 3 };
    }

    // 2. Double-checked locking is only correct when the field is volatile.
    private static volatile int[] cached;

    static int[] cached() {
        int[] local = cached;                       // one read of the volatile field
        if (local == null) {
            synchronized (Main.class) {
                local = cached;
                if (local == null) { cached = local = new int[] { 4, 5, 6 }; }
            }
        }
        return local;
    }

    // 3. final fields are frozen when the constructor ends, so readers see them built.
    record Point(int x, int y) { }

    // 4. The bug: publishing this before the object is finished.
    static class Leaky {
        static Leaky escaped;
        final int ready;
        Leaky() {
            escaped = this;                          // visible now, still half-built
            ready = 42;                              // another thread may not see this yet
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] readers = new Thread[4];
        for (int i = 0; i < readers.length; i++) {
            readers[i] = new Thread(() -> System.out.println(Thread.currentThread().getName()
                    + " holder=" + Holder.DATA.length
                    + " dcl=" + cached()[0]
                    + " final=" + new Point(1, 2)), "reader-" + i);
            readers[i].start();
        }
        for (Thread t : readers) t.join();

        new Leaky();
        System.out.println("safe publication : every reader saw a fully built value");
        System.out.println("leaky escape     : ready=" + Leaky.escaped.ready + " here, but not guaranteed elsewhere");
    }
}

Build it fully, then publish it safely. A half-built object is visible to other threads.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.