The Java Memory Model

The rules that decide when a write by one thread becomes visible to another, expressed as the happens-before relationship.

The problem it solves

// Thread A
data = 42;
ready = true;

// Thread B
if (ready) {
    System.out.println(data);    // may print 0
}

Both the compiler and the processor may reorder independent instructions, and each processor core has its own caches. Without a rule, thread B could see ready as true while still reading a stale data. The Java Memory Model defines exactly which reorderings are permitted and when a write must be visible.

happens-before

If action A happens-before action B, then everything A did is visible to B. It is not about wall clock time; it is a guarantee about visibility and ordering.

RuleGuarantee
Program orderWithin one thread, each statement happens-before the next
Monitor lockReleasing a lock happens-before another thread acquires it
VolatileA write to a volatile field happens-before every later read of it
Thread startthread.start() happens-before anything in that thread
Thread joinEverything in a thread happens-before join() returns
Final fieldsFields set in a constructor are visible once it completes safely
TransitivityIf A happens-before B and B happens-before C, then A happens-before C

Fixing the example

private int data;
private volatile boolean ready;      // volatile creates the edge

// Thread A
data = 42;
ready = true;         // the write to data cannot move after this

// Thread B
if (ready) {          // if this reads true...
    System.out.println(data);   // ...42 is guaranteed to be visible
}

A volatile write publishes everything written before it; a volatile read sees all of it. This is why one volatile flag can safely guard several ordinary fields.

Locks give the same guarantee

synchronized (lock) {
    data = 42;
}
// release happens-before the next acquire

synchronized (lock) {
    System.out.println(data);   // 42 is visible
}

Note that both threads must use the same lock. Locking different objects creates no ordering between them at all.

Safe publication

// Unsafe: another thread may see a partially constructed object
public class Registry {
    private static Registry instance;

    public static Registry get() {
        if (instance == null) {
            instance = new Registry();   // the reference may be visible first
        }
        return instance;
    }
}

An object is safely published when it is made visible in a way that guarantees other threads see it fully constructed. The reliable ways are:

  • Initialise it in a static initialiser.
  • Store it in a volatile field or an AtomicReference.
  • Store it in a final field of a properly constructed object.
  • Store it in a field guarded by a lock, and read it under the same lock.
  • Put it into a concurrent collection, which publishes safely.
// The static holder idiom: the JVM guarantees a class is initialised once
public class Registry {

    private static class Holder {
        static final Registry INSTANCE = new Registry();
    }

    public static Registry get() {
        return Holder.INSTANCE;
    }
}

The final field guarantee

public final class Config {

    private final Map<String, String> values;

    public Config(Map<String, String> values) {
        this.values = Map.copyOf(values);
    }

    public String get(String key) {
        return values.get(key);
    }
}

Provided the constructor does not let this escape before it finishes, every thread that obtains a reference to the object sees its final fields fully initialised, with no synchronisation at all. This is a large part of why immutable objects are the simplest answer to concurrency.

// Leaking this from a constructor breaks the guarantee
public Config(Registry registry) {
    registry.register(this);       // other threads can see a half built object
    this.values = load();
}

Atomicity of individual operations

OperationAtomic
Reading or writing any type up to 32 bitsYes
Reading or writing a referenceYes
Reading or writing a non volatile long or doubleNot guaranteed
Reading or writing a volatile long or doubleYes
count++No, it is three operations

On a 32 bit JVM a long write can be split into two halves, so another thread could observe a value that was never assigned. Declaring it volatile or protecting it with a lock removes the possibility.

Reordering in practice

Source order          A possible execution
data = 42;            ready = true;      compiler and CPU may swap these
ready = true;         data = 42;         because they are independent

Within a single thread the result is always as if nothing moved. The reordering is only observable from another thread, which is why concurrency bugs of this kind are so difficult to reproduce.

Practical guidance

  1. Prefer immutability. An object that never changes needs no memory model reasoning.
  2. Prefer confinement. State touched by one thread has no visibility problem.
  3. Publish safely when a shared object must be handed over.
  4. Use the concurrent utilities, which are already correct.
  5. Use volatile for flags, and locks or atomics for anything compound.
  6. Document which lock guards which field.
public class Statistics {

    private final Object lock = new Object();

    // Guarded by lock
    private long total;
    private int count;

    public void record(long value) {
        synchronized (lock) {
            total += value;
            count++;
        }
    }

    public double average() {
        synchronized (lock) {
            return count == 0 ? 0 : (double) total / count;
        }
    }
}

Common mistakes

  • Assuming a write is visible simply because it happened earlier in time.
  • Synchronising on different objects and expecting an ordering between them.
  • Publishing an object through a plain field.
  • Letting this escape from a constructor.
  • Treating a non volatile long as atomic.
  • Testing on one machine and concluding the code is correct; different processors reorder differently.

Practice

  1. Write the flag and data example without volatile and explain why it may misbehave.
  2. List three ways to publish an object safely.
  3. Why does a final field need no synchronisation to be visible?
  4. Explain why locking two different objects gives no happens-before edge.
  5. Show how leaking this from a constructor breaks the final field guarantee.

Conclusion

The memory model is a set of happens-before edges created by locks, volatile fields, thread start and join, and final fields. Build your program from immutable objects and safe publication, and most of these rules never have to be applied by hand.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.