Several languages offer a qualifier that marks a shared variable so its reads and writes participate in the memory model - Java's and C#'s volatile, or a C++ atomic used with release/acquire ordering. What does such a marking guarantee, what does it not guarantee, and how does C's volatile differ?
answer
- freshness + single-variable atomicity + release/acquire ordering
- the flag carries the data written before it
- increment still races - no compound atomicity
- C volatile = device registers, not threads
- relaxed vs release/acquire vs total order
basics
~20 sIt guarantees the variable is really read and written each time, that a write becomes visible to a later read in finite time, and that the write acts as a release and the read as an acquire, so earlier writes are visible too. It does not make read-modify-write atomic. C's volatile gives none of this.
solid answer
~60 sA memory-model visibility qualifier gives three things. First, no caching in a register: each read is a real load and each write a real store, so a waiting loop terminates. Second, single-variable atomicity: a reader sees a whole value, never a half-written one. Third, and most important, ordering: the write is a release and the read an acquire, so everything the writer did before the write is visible to a reader that observes it. That third property is what makes it usable for publishing data, not just for flags. What it does not give is compound atomicity: `x = x + 1` is still a separate read and write, so two threads can lose an update - that needs a compare-and-swap or a lock. It also does not keep several variables mutually consistent. C and C++'s `volatile` is a different keyword with the same spelling: it means 'do not optimize away this access', intended for memory-mapped I/O and signal handlers, and provides no cross-thread ordering at all; there you use atomics instead.
go deeper
Know it makes a shared flag actually visible to other threads and that it does not make increments safe.
Separate the three guarantees - freshness, single-variable atomicity, release/acquire ordering - and give the flag-publishes-the-data example.
Add the cost and contention story, when a plain field plus a lock is preferable, and the C-versus-managed-language volatile distinction.
Discuss ordering tiers as an explicit design choice, and prefer publishing immutable snapshots through one reference over sprinkling qualifiers across mutable fields.
## Three separate guarantees When a language marks a variable as participating in its memory model, three properties come bundled and it is worth separating them. **Freshness.** The compiler may not keep the variable in a register across operations. Each read is a real load and each write a real store, so a loop waiting for a change actually observes it, and a store becomes visible to other threads in finite time. **Single-variable atomicity.** A read returns some value that was written, never a mixture of two. This matters for values wider than a machine word, where a plain write could otherwise be observed half-updated. **Ordering.** The write behaves as a release and the read as an acquire. If a reader observes the value written, a happens-before edge is created, so every write the writer performed before it is also visible. This is the property that turns a marked variable from a flag into a publication mechanism: the flag carries the data. ## What it does not give **Compound atomicity.** An increment is a load, an add, and a store. Two threads can both load 5, both store 6, and one update is lost. The qualifier orders operations; it does not fuse them. Read-modify-write needs an atomic operation or a lock. **Invariants across several variables.** Marking two fields does not make updating them a single step. A reader can see the new value of one and the old value of the other, unless the design designates one release/acquire variable that publishes the rest. **Free performance.** A release store and an acquire load compile to ordering constraints and often to a fence. On a hot path a marked variable is measurably more expensive than a plain one, and one written by several cores becomes a contention point on a single cache line. ## The C and C++ trap C's `volatile` predates threads. It means the compiler must not eliminate, invent, or reorder accesses to that object relative to other volatile accesses - designed for device registers, `setjmp`, and signal handlers. It says nothing about other threads, imposes no hardware ordering, and does not make the access atomic. Using it as a cross-thread flag is a well-known bug; the correct tool in C11/C++11 and later is an atomic object with an explicit memory order. Java and C# instead defined `volatile` inside their memory models, which is why identical spelling means substantially different things - a favourite interview probe for candidates who have worked in more than one language. ## Choosing it A visibility qualifier is the right tool when exactly one variable carries the synchronization and the update is a plain assignment: a shutdown flag, a reference to a freshly built immutable configuration, a completion signal, the guard in a correctly written double-checked initialization. It is the wrong tool for counters, for check-then-act sequences, and for keeping several fields consistent - all of which need atomic read-modify-write or mutual exclusion. ## Weaker and stronger orderings Some languages let you choose the ordering explicitly. A *relaxed* atomic gives atomicity and freshness but no ordering edge - legitimate for a counter inspected only at the end. *Release/acquire* gives the pairwise edge described above and is the common default. The strongest setting additionally places all such operations into one global order, which a few algorithms need and which costs the most. Knowing these tiers exist, and that release/acquire is what publication requires, is usually the depth an interviewer is probing for.
- A counter incremented by many threads through such a marked variable comes out low. Why, and what would you use instead?An increment is a read, an add and a write; the qualifier orders each access but does not make the trio indivisible, so two threads can read the same value and both write back the same successor, losing an update. The fix is an atomic read-modify-write such as fetch-and-add or a compare-and-swap retry loop, or a mutex around the update. Under high contention, per-thread counters summed on read scale better than either.
- Does marking a reference to an object make the object's fields safely visible?Yes for fields written before the marked store, provided the publishing thread finishes all initialization first and then stores the reference. The release/acquire pair orders those earlier writes for any reader that observes the new reference. It does not protect fields mutated after publication - those are ordinary shared mutable state again and need their own synchronization, which is why publishing immutable objects is the safer pattern.
saying these in an interview costs you the question
- Calling it a lightweight lock or claiming it makes a block of code atomic
- Using it for counters and believing increments cannot be lost
- Assuming C's volatile provides thread safety because Java's does
- Saying it only prevents caching, missing that it also publishes everything written before the store
- Marking many fields instead of publishing through one variable, then expecting those fields to be mutually consistent