The Java Memory Model (JMM) defines when one thread's writes become visible to another. Ignore it and you get bugs that vanish under a debugger and reappear only under load. Here are six things worth internalizing.
1. Threads Can Cache Variables
A thread may hold a copy of a field in a register or CPU cache. A write by one thread is not guaranteed to be seen by another unless the JMM says so. This is why a plain flag can loop forever:
boolean running = true; // another thread sets this false... while (running) { } // ...but this thread may never notice
2. volatile Guarantees Visibility
Marking the flag volatile forces reads and writes to go to main memory, so the change is seen immediately:
private volatile boolean running = true;
volatile fixes visibility. It does not make compound actions atomic.
3. volatile Is Not a Lock
count++ on a volatile int is still three operations โ read, add, write โ and two threads can interleave them and lose an update. For atomic compound actions use AtomicInteger or synchronized:
private final AtomicInteger count = new AtomicInteger(); count.incrementAndGet(); // atomic
4. happens-before Is the Real Rule
The JMM's core concept is happens-before: if action A happens-before B, A's effects are visible to B. Unlocking a monitor happens-before locking it; a volatile write happens-before a subsequent read of it; a thread's start() happens-before its run. These edges are what make concurrent code correct.
5. Final Fields Are Special
Correctly constructed final fields are guaranteed visible to other threads after construction, without synchronization. This is why immutable objects are inherently thread-safe โ there is nothing to see change.
6. Prefer Higher-Level Tools
You rarely need to reason about the JMM by hand. java.util.concurrent โ ConcurrentHashMap, BlockingQueue, AtomicReference, executors โ encodes the correct happens-before edges for you. Reach for those before hand-rolling synchronized blocks.
Summary
Multithreading bugs are usually visibility bugs: one thread cannot see another's writes. Remember that threads cache, volatile restores visibility (but not atomicity), happens-before is the underlying rule, and final plus immutability sidesteps the whole issue. When in doubt, let java.util.concurrent do the reasoning for you.