volatile guarantees visibility and ordering for a single field: a write to a volatile field is immediately visible to any thread that subsequently reads it, and reads/writes around it are not reordered across the volatile access. It does not provide atomicity for compound operations like count++.
What it guarantees
A volatile write happens-before every later volatile read of the same field. This single edge gives you two things:
- Visibility: the reading thread sees the latest value, not a stale cached copy.
- Ordering (the piggyback effect): everything the writing thread did before the volatile write is also visible to the reader after it reads the volatile field. Since JSR-133, a volatile write acts as a release barrier and a volatile read as an acquire barrier, so non-volatile writes cannot be reordered past them.
class Holder {
private int data;
private volatile boolean ready;
void publish(int v) {
data = v; // plain write
ready = true; // volatile write — release
}
int read() {
if (ready) { // volatile read — acquire
return data; // guaranteed to see v, not 0
}
return -1;
}
}
data is published safely because of the volatile ready, even though data itself is not volatile. That is the happens-before piggyback at work.
What it does NOT guarantee
volatile is not a lock. It cannot make a read-modify-write atomic:
volatile int count;
void inc() { count++; } // BROKEN: read, +1, write — three steps, can interleave
Two threads can both read count == 5, both write 6, and one increment is lost. For atomic compound updates use AtomicInteger (CAS) or a lock.
Also note: 64-bit long/double non-volatile writes may be split into two 32-bit writes ("word tearing"); marking them volatile makes them atomic, which is a secondary guarantee worth mentioning.