Explain the happens-before guarantee of a volatile write/read and how you can 'piggyback' the publication of other data on it.
answer
- volatile write hb subsequent read of same field
- happens-before is transitive
- write plain fields, then the volatile flag last
- release write / acquire read
- only writes BEFORE the publish are covered
basics
~20 sA volatile write happens-before any later read of that same field. So everything a thread wrote before writing the volatile flag becomes visible to another thread once it reads the flag — you can publish plain data by writing the volatile flag last.
solid answer
~50 sUnder the Java Memory Model, a write to a volatile field happens-before every subsequent read of that same field by another thread. happens-before is a transitive ordering: if action X happens-before the volatile write, and the volatile read happens-before action Y, then X is visible to Y. That lets you 'piggyback': a thread writes several ordinary (non-volatile) fields, then writes a single volatile flag last; a second thread that reads the flag and sees the new value is then guaranteed to see all those preceding ordinary writes too. This is the basis of safe publication patterns and of double-checked locking with a volatile field. The volatile write has release semantics (earlier writes can't move after it) and the volatile read has acquire semantics (later reads can't move before it), which is what makes the piggyback sound. The catch: only writes that happen-before the volatile write are covered — mutations made after publishing aren't.
code
java · 16 linesclass Holder {
private int data; // plain
private volatile boolean ready; // publish flag
void publish(int value) {
data = value; // (1) ordinary write
ready = true; // (2) volatile release; (1) hb (2)
}
int read() {
if (ready) { // (3) volatile acquire; (2) hb (3)
return data; // guaranteed to see (1)
}
return -1;
}
}go deeper
Aware that volatile makes a write visible to a later read, without necessarily articulating happens-before.
States the volatile happens-before rule and can use a flag to publish data, but may be fuzzy on the 'only writes before the publish' limitation.
Explains transitivity, acquire/release semantics, the piggyback publication pattern, and the post-publish-mutation pitfall; links it to double-checked locking.
Reasons rigorously about the JMM synchronization order, when release/acquire is sufficient vs needing stronger ordering, and designs lock-free publication protocols with correct fences.
## happens-before: the ordering relation The **Java Memory Model (JMM)** defines which writes a read is guaranteed to observe via the **happens-before** relation. If action A *happens-before* action B, then A's memory effects are visible to B, and A appears to occur before B. happens-before is **transitive**: A hb B and B hb C imply A hb C. Within a single thread, program order gives happens-before. Across threads, you need a *synchronization* action to create an inter-thread edge. The **volatile rule** is one such edge: *a write to a volatile field happens-before every subsequent read of that same field.* 'Subsequent' is by the synchronization order — once a reader observes the value the writer put there. ## Acquire / release framing A volatile **write** has **release** semantics: no memory operation that appears before it in program order may be reordered to *after* it. A volatile **read** has **acquire** semantics: no memory operation after it may be reordered to *before* it. Combine them across threads and you get: everything before the release is visible after a matching acquire. ## Piggybacking (safe publication) Because happens-before is transitive, you can publish *ordinary* data through a single volatile flag: ```java class Config { private int timeout; // plain field private String url; // plain field private volatile boolean ready = false; // the publish flag void init() { // writer thread timeout = 30; // (1) url = "https://..."; // (2) ready = true; // (3) volatile WRITE - release } void use() { // reader thread if (ready) { // (4) volatile READ - acquire // guaranteed to see (1) and (2): timeout==30, url set connect(url, timeout); } } } ``` The chain: (1) hb (3) by program order; (3) hb (4) by the volatile rule (the reader saw `ready == true`); (4) hb the use of `url`/`timeout` by program order. Transitively (1),(2) hb the reads — so the reader sees the fully-initialized config even though `timeout`/`url` are *not* volatile. The single volatile flag 'carries' the others. This is exactly why **double-checked locking** requires the instance field to be volatile: the volatile write of the reference piggybacks the object's fully-constructed state, preventing a reader from seeing a non-null but partially-constructed object. ## The crucial limitation Only writes that **happen-before** the volatile write are published. If you mutate the published data *after* writing the flag: ```java ready = true; // publish url = "changed"; // NOT covered - happens AFTER the volatile write ``` the later mutation has no happens-before edge to the reader and may be invisible or seen at an arbitrary time. So the discipline is: **fully initialize, then publish, then don't mutate** (or use immutable objects, whose `final` fields get their own safe-publication guarantee). ## Why this matters in practice Piggybacking lets you avoid making *every* shared field volatile or synchronized — you pay the barrier cost on one flag and get visibility for a whole batch of preceding writes. It underlies safe-publication idioms, lock-free hand-offs (one producer, one consumer via a volatile 'published' marker), and the correctness of double-checked locking.
- Why must the instance field in double-checked locking be volatile?The volatile write of the reference piggybacks the object's constructor writes (happens-before), so a second thread that reads a non-null reference is guaranteed to see a fully-constructed object. Without volatile, reordering can publish a non-null but partially-initialized instance.
- If I write the volatile flag and THEN mutate the published object, is that mutation visible?Not guaranteed. Only writes that happen-before the volatile write are published. Mutations after the flag write have no happens-before edge to the reader, so they may be invisible. Fully initialize before publishing, then treat the object as immutable.
Sealing a package (volatile write) after you've put all the items inside: whoever opens the sealed package (volatile read) is guaranteed to find everything that was inside before sealing. Anything you try to slip in after sealing isn't guaranteed to be there.
saying these in an interview costs you the question
- Thinking a volatile read sees writes made AFTER the volatile write that published it.
- Believing every shared field must itself be volatile, missing that one flag can publish many.
- Confusing happens-before (an ordering/visibility guarantee) with mutual exclusion.
- Forgetting transitivity is what makes the piggyback work.