Java Concurrency & Async Programming · Lesson 2

Java Memory Model, Visibility, and Happens-Before Explained

Understand atomicity, visibility, ordering, data races, volatile fields, safe publication, and happens-before reasoning in Java backend code.

Source order is not a complete description of what another thread may observe. Compilers, the JVM, processors, and caches may reorder or delay observations while preserving the rules of the Java Memory Model (JMM). Correct concurrent code therefore reasons from defined synchronization actions, not timing guesses.

Quick answer

The JMM separates atomicity, visibility, and ordering. A happens-before relationship guarantees that effects before one action are visible to and ordered before effects after another action. Lock release/acquisition, volatile write/read, thread start/join, and class initialization establish important edges. volatile supplies specific visibility and ordering guarantees, but it does not make a compound read-modify-write such as count++ atomic.

Learning objectives

  • Build a happens-before chain for publication, handoff, and shutdown without relying on sleeps or observed timing.
  • Distinguish visibility, ordering, single-operation atomicity, and compound business atomicity when selecting a Java primitive.

Prerequisites

Begin with Java Concurrency for Backend Systems. Familiarity with fields, objects, and threads is enough. The Java Concurrency course places this lesson before locks, executors, and virtual threads; the topic cluster lists every related article.

Core model: edges, not freshness guesses

If action A happens-before action B, every effect visible to A is ordered before B. Program order creates edges within one thread. Synchronization order can connect threads. Happens-before is transitive, so several edges form a proof.

Important examples include:

  • Unlocking a monitor happens-before a later lock of the same monitor.
  • Writing a volatile field happens-before a later read of that same field.
  • Actions before Thread.start() happen-before actions in the started thread.
  • Actions in a thread happen-before another thread successfully returns from join().
  • Static initialization completes before normal use of that class.

Without a relevant edge, repeated reads are not a reliable publication protocol. “It always changed within a millisecond in testing” is not a JMM guarantee.

Java 21 baseline example

This one-writer publication pattern writes ordinary data before the volatile readiness flag. A reader that observes ready == true also observes the earlier payload write.

final class PublishedConfig {
    private String endpoint;
    private volatile boolean ready;

    void publish(String value) {
        endpoint = value;
        ready = true;
    }

    String read() {
        if (!ready) throw new IllegalStateException("not ready");
        return endpoint;
    }
}

This does not support arbitrary concurrent writers, does not make a mutable object graph permanently safe, and does not turn several fields into a transactional update. Prefer immutable values and a single volatile reference when publishing a coherent configuration snapshot.

Atomicity is a separate question

volatile int count does not make count++ atomic. Increment expands conceptually into a read, calculation, and write; two threads can read the same old value and overwrite one another. AtomicInteger.incrementAndGet() can make that one counter transition atomic. A compound invariant such as “debit one account and credit another” still needs a larger lock or durable transaction.

Likewise, a concurrent collection protects only its specified operations. A mutable value retrieved from the map can still be raced, and a rule spanning two maps is not automatically atomic. Continue with ConcurrentHashMap Explained for its documented compound methods.

Failure scenario: shutdown that sometimes hangs

A worker loops on a plain boolean running. Another thread sets it to false, but the worker has no synchronization edge requiring it to observe the write. A load test passes on one machine and hangs on another. Making the flag volatile fixes visibility for that flag, but it still does not unblock an interruptible queue wait. A robust shutdown protocol sets state, interrupts blocking work when appropriate, preserves interruption policy, and waits with a bounded join.

Common misconceptions

  • volatile is not a general replacement for a lock.
  • Atomic reads do not make a read-check-write sequence atomic.
  • “Effectively immutable” still requires safe publication.
  • Thread-safe collections do not protect mutable contents or cross-structure business invariants.
  • sleep() creates delay, not a happens-before edge between application actions.

Production validation

Express invariants as repeatable concurrent tests with latches or barriers rather than sleeps. Run stress tests many times across optimized builds, but remember that failure absence is not a proof. Review every shared field for ownership and publication. Use JFR and jcmd Thread.print to diagnose live lock or wait behavior, then connect JVM evidence to request outcomes. For durable state, verify database concurrency controls separately; the JMM governs Java memory, not remote processes.

Next compare synchronized, Locks, and Atomic Variables, then learn how ExecutorService and Work Queues introduce explicit task ownership.

FAQ

Does volatile make the referenced object immutable?

No. It governs reads and writes of the reference and their ordering effects. Mutating the referenced object still needs its own safety policy.

Is a data-race-free program automatically correct?

No. It receives strong JMM guarantees, but higher-level ordering and business invariants can still be wrong.

Can tests prove a missing race?

Testing can expose defects and validate designed protocols. The proof still comes from the synchronization and ownership rules that establish the necessary ordering.

Return to the Java Concurrency learning path, browse the Java Concurrency topic, and study Optimistic Locking for a durable multi-process concurrency boundary.

Official sources

Knowledge check

Check your understanding

Answer both questions correctly to mark this lesson as mastered. You can retry without penalty.

1. A writer stores ordinary payload data and then writes a volatile ready flag; what does a reader gain after observing ready?

2. Why can volatile count++ still lose increments?