Loop doesn't see value changed by other thread without a print statement
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
If a Java loop only stops when you add System.out.println, the real bug is not in printing. It is a memory-visibility problem: one thread updates a value, but another thread is allowed to keep reading a stale copy because the code has no proper happens-before relationship.
Why the Loop Can Miss the Update
In Java, threads do not automatically see each other's writes immediately. The Java Memory Model allows:
- CPU caching
- JIT optimizations
- Reordering of reads and writes
That means a loop like this is broken:
The worker thread is not guaranteed to observe the write to done. It may keep reading a cached value or allow the JIT to optimize the loop in a way that assumes done does not change.
Why println Seems to Fix It
System.out.println changes timing, and it also synchronizes internally because PrintStream methods are synchronized. That extra synchronization can create enough memory barriers to make the updated value visible by accident.
This is why adding logging sometimes makes a concurrency bug "go away." The code was never correct. The print statement only changed the execution conditions.
The Correct Fix: volatile
If the variable is a simple shared flag, mark it volatile:
volatile gives you two important guarantees:
- A write by one thread becomes visible to readers in other threads
- Reads and writes of that variable are not freely reordered across the visibility barrier
For a stop flag, that is usually exactly what you need.
When volatile Is Not Enough
volatile works for visibility, but it does not make compound operations atomic. For example, this is still unsafe:
count++ is read, modify, write. Multiple threads can still race. In that case, use synchronization or an atomic type:
Other Correct Approaches
You can also fix visibility with:
- '
synchronizedblocks or methods' - '
Lockimplementations fromjava.util.concurrent.locks' - Higher-level concurrency constructs such as
CountDownLatch
For example, a latch expresses one-time completion much more cleanly than a spin loop:
Common Pitfalls
- Assuming a debug print proves the code is correct.
- Using a plain shared boolean between threads without
volatileor synchronization. - Using
volatilefor non-atomic compound operations and expecting it to solve race conditions. - Writing busy loops when a higher-level synchronization primitive would express the intent more clearly.
Summary
- A loop missing another thread's update is a visibility bug.
- '
printlnonly changes timing and synchronization side effects; it is not a real fix.' - Use
volatilefor simple shared flags. - Use atomics or locks for compound updates.
- In many cases, a latch, queue, or other concurrency primitive is better than a spin loop.
Related reading
- Loop over javascript object whilst running async calls within
- Looping and asynchronous connections in objective-c
- low level programming How does the OS start a new thread/process?
- Main Thread Checker UI API called on a background thread -UIApplication applicationState
- Lossless JPEG Rotate 90/180/270 degrees in Java?
- Mac OS X and multiple Java versions
- Lost connection to MySQL server at ''reading initial communication packet'', system error 0
- Lost connection to MySQL server during query error with Rails3, octopus replication gem

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.