Short answer
Processors cache variables locally (L1/L2) and reorder instructions, so one thread's changes may not be immediately visible to others. In Java, volatile ensures every write is visible to every subsequent read (a happens-before relationship) and prevents reordering around it. In C/C++, volatile provides no cross-thread visibility guarantee; use std::atomic instead.
Explanation
Without synchronization, Thread A may enter a while (!flag) {} loop. If Thread B changes flag to true, Thread A might never see the change because the processor placed flag in a local register and assumed it would not change inside the loop. In Java, volatile prevents the compiler from keeping the value in a register and prevents nearby instruction reordering with memory barriers/fences, creating a happens-before relationship between the write and every later read. This does not literally mean "writing to main memory": cache-coherence protocols such as MESI coordinate visibility across cores; the actual issue is register caching and reordering. In C/C++, volatile gives no thread-synchronization guarantees; use std::atomic with the appropriate memory_order.
Code example
// Java: Ensures changes are visible to all threads and prevents reordering around it (but is not atomic count++):
private volatile boolean isRunning = true;Common mistakes
- Believing that volatile provides atomicity; it solves visibility only and does not protect count++ from race conditions.