How do the memory effects of a synchronized block compare to those of a volatile field, and when does each carry an additional guarantee the other does not?
answer
- same acquire/release foundation; different bundling
- monitor acquire/release ≈ volatile read/write (memory-wise)
- synchronized adds mutual exclusion → atomic compound ops
- volatile = visibility only, no atomicity (count++ loses updates)
- double-checked locking needs BOTH volatile + synchronized
basics
~20 sBoth create the same acquire/release visibility: entering/reading refreshes from memory, exiting/writing flushes to memory. synchronized adds mutual exclusion, so it can make a multi-step operation atomic. volatile only gives visibility for a single field, with no locking and no atomicity for compound actions.
solid answer
~50 sBoth rely on the same acquire/release foundation. A monitor acquire and a volatile read are acquire barriers; a monitor release and a volatile write are release barriers, and both produce happens-before edges (monitor unlock→lock; volatile write→read of the same field). The difference is mutual exclusion. synchronized serializes access, so a compound read-modify-write (like count++ or check-then-act) is atomic within the lock — no other thread can interleave. volatile gives no exclusion: each access is individually visible, but two threads can still interleave around it, so count++ on a volatile loses updates. So volatile carries the visibility/ordering guarantee more cheaply (no blocking, no lock object) when you only publish/observe a single value; synchronized carries the extra atomicity guarantee when you need an indivisible compound operation. Neither is strictly stronger on memory effects; synchronized is strictly stronger on exclusion.
go deeper
Can say both make changes visible across threads, that volatile is for a single field and synchronized also locks. Detailed equivalence is beyond this level.
States that volatile read/write mirror monitor acquire/release for visibility, and that synchronized additionally provides mutual exclusion for atomic compound operations.
Articulates the shared acquire/release foundation, the precise atomicity difference (count++), when each is preferable, and that double-checked locking needs both.
Reasons across the full toolbox (synchronized, volatile, Atomic/CAS, VarHandle access modes and fences), advises on cost/contention trade-offs, and designs publication protocols and memory-model-correct lock-free code.
## Two tools, one memory-model foundation Both `synchronized` and `volatile` are built on the same JMM primitives — **acquire** and **release** barriers — but they bundle them differently. ### Define the two barriers (recap) - **Acquire barrier:** when crossing it, the thread refreshes its view of shared memory (invalidate + re-read), and later reads cannot move before it. - **Release barrier:** when crossing it, the thread's prior writes are flushed (made visible), and earlier writes cannot move after it. ### Where each tool places the barriers **synchronized:** - Entry = **monitor acquire** = acquire barrier. - Exit = **monitor release** = release barrier. - Happens-before rule: an unlock on a monitor **happens-before** every subsequent lock of the *same* monitor. - **Plus mutual exclusion:** only one thread holds the monitor at a time. **volatile:** - A **volatile read** = acquire barrier. - A **volatile write** = release barrier. - Happens-before rule: a volatile write to a field **happens-before** every subsequent volatile read of the *same* field. - **No mutual exclusion:** nothing is serialized. ### The shared guarantee: visibility & ordering For *publishing one value and observing it*, the two are equivalent in memory effect. If thread A writes data then writes a volatile flag, a thread B that reads the volatile flag (and sees it set) is guaranteed to also see `data` — exactly as if A had released and B had acquired a monitor. This is because the release flushes *all prior writes*, and the matching acquire observes them. So the often-quoted line is: **a volatile write/read pair has the same memory semantics as a monitor release/acquire pair.** ### Where synchronized is strictly stronger: atomicity of compound actions Visibility is not atomicity. Consider `count++` where `count` is volatile: ```java volatile int count; count++; // read count, add 1, write count — THREE steps ``` Each individual read and write is visible, but two threads can both read 5, both compute 6, and both write 6 — a **lost update**. `volatile` does nothing to prevent the interleave. With `synchronized`: ```java synchronized (lock) { count++; } ``` the lock serializes the whole read-modify-write, so it is **atomic** with respect to other holders of the same lock. The same applies to **check-then-act** (e.g., lazy init, `if (instance == null) instance = new X()`), where volatile alone is insufficient unless paired with a careful pattern like double-checked locking (which itself combines volatile *and* synchronized). ### Where volatile is preferable: cost and simplicity When you only need visibility of a single field (a stop flag, a published reference, a generation counter you only read), `volatile` is cheaper: no lock object, no blocking, no risk of deadlock, no contention. Using `synchronized` there works but adds unnecessary serialization. So volatile is the right tool for the *publish-once / observe* pattern. ### A table to lock it in | Property | synchronized | volatile | |---|---|---| | Acquire barrier | on entry | on each read | | Release barrier | on exit | on each write | | Visibility / ordering (happens-before) | yes (same monitor) | yes (same field) | | Mutual exclusion | **yes** | no | | Atomic compound op (read-modify-write) | **yes** | no | | Blocking / can deadlock | yes | no | | Typical use | critical sections, invariants over multiple fields | single-flag/reference publish & observe | ### The nuance interviewers like - Neither is 'stronger' on **memory effects** — both give the full acquire/release visibility. - `synchronized` is strictly stronger on **exclusion/atomicity**. - Double-checked locking needs **both**: `volatile` for the visibility of the reference, `synchronized` for the atomic check-then-construct. ### Related modern primitives `java.util.concurrent.atomic` (e.g., `AtomicInteger`) gives atomic compound operations (CAS-based `incrementAndGet`) *with* volatile-like visibility but *without* a lock — a middle ground. `VarHandle` exposes explicit acquire/release/opaque/plain access modes and standalone fences for fine-grained control. These build on the same acquire/release model.
- Why does double-checked locking require the singleton field to be volatile?Without volatile, another thread can see a non-null reference whose constructor writes haven't been published yet (the object reference can be visible before its fields are), returning a partially-constructed object. Making it volatile makes the publishing write a release so the construction is fully visible before the reference is.
- If both give the same visibility, why not always use volatile and avoid locks?Because volatile gives no mutual exclusion, so it can't make a multi-step operation atomic. Any invariant spanning more than one field, or any read-modify-write, needs a lock (or an atomic/CAS primitive). volatile only safely handles single-variable publish/observe.
saying these in an interview costs you the question
- Claiming volatile makes count++ thread-safe
- Saying synchronized has weaker memory effects than volatile (they're equivalent on visibility)
- Using synchronized for a simple flag where volatile suffices, or vice versa for compound ops
- Forgetting double-checked locking needs the field to be volatile