Java Memory Semantics of Atomic Variables

Java atomic variables provide atomic read-modify-write operations plus defined visibility and ordering guarantees between threads. Correct concurrent designs must distinguish atomicity of the variable itself from visibility and ordering of other shared state.

An atomic variable such as `AtomicInteger` combines an indivisible operation with memory-consistency effects. Operations such as `get`, `set`, `compareAndSet`, and `incrementAndGet` are specified so that concurrent threads observe the atomic variable according to Java's memory model rather than relying on accidental hardware behavior.

The important distinction is that atomicity applies to the atomic variable operation, while the memory-ordering effects can also establish relationships with accesses to other variables. For example, a thread can write ordinary shared data and then publish a state change through an atomic operation; another thread that observes the corresponding atomic state can acquire visibility of the earlier writes when the relevant happens-before relationship is established.

Conceptually, `get` has volatile-read semantics and `set` has volatile-write semantics. Atomic read-modify-write operations such as `compareAndSet` and `getAndIncrement` provide atomicity together with volatile-style memory effects. These guarantees prevent certain compiler, processor, and cache-level reorderings from making correctly synchronized communication appear inconsistent.

Java also exposes weaker operations in some APIs. For example, `lazySet` is weaker than an ordinary volatile-style `set`, and `weakCompareAndSet` may fail spuriously depending on the API and operation being used. These distinctions matter when designing lock-free algorithms because choosing a weaker access mode changes what ordering guarantees the program receives.

The Java Memory Model describes correctness in terms of relationships such as happens-before, not simply whether a value eventually reaches another CPU core. An atomic counter therefore does not automatically make an entire compound invariant thread-safe: if several fields must change together, an atomic variable alone may be insufficient.

Key points

Interview questions

What memory guarantees do Java atomic variables provide beyond atomicity?

Atomic variables provide defined memory-consistency effects in addition to indivisible operations. Their ordinary `get` and `set` operations have volatile-read and volatile-write semantics, while atomic read-modify-write operations also carry strong synchronization effects. These effects can make writes to other variables visible when an appropriate happens-before relationship is formed. Therefore, an atomic variable can serve as both shared state and a synchronization mechanism.

Why does this publication pattern make `data` visible to the reader?

The writer stores `data` before executing `ready.set(1)`. The reader later observes the published value through `ready.get()`. Because the atomic accesses have volatile-style memory effects, the relevant write-read synchronization creates a happens-before relationship from the earlier write to `data` to the reader's subsequent access. The guarantee is therefore about memory ordering, not merely cache propagation.

Why is `volatile int counter; counter++` not a thread-safe counter?

A volatile read and volatile write are individually visible, but `counter++` consists of a read, an addition, and a write. Two threads can read the same old value and then both write the same incremented result, losing an update. An atomic operation such as `incrementAndGet()` performs the read-modify-write transition indivisibly.

What is the difference between `compareAndSet` and `weakCompareAndSet`?

`compareAndSet` succeeds when the current value equals the expected value and otherwise reports failure. Weak CAS may fail spuriously even when the expected value matches, depending on the API and operation. A weak CAS is therefore normally used inside retry loops or algorithms designed to tolerate spurious failure. The exact memory-ordering guarantees should also be checked for the particular API and access mode rather than assumed from the method name alone.

When would you choose an atomic variable instead of `synchronized`?

An atomic variable is useful when the shared state can be expressed as a small atomic transition, such as incrementing a counter or replacing a reference conditionally. It can avoid blocking and can form the basis of lock-free algorithms. `synchronized` is often more appropriate when several fields or operations must change together as one invariant-preserving critical section. The choice should follow the required correctness and contention model rather than assuming lock-free is inherently better.