Understanding the Impact of Print Statements, sleep, and Integer on Java Thread Safety
This article explores how adding print statements, using Thread.sleep, and employing Integer objects affect thread safety in Java, illustrating why a non‑volatile flag can cause infinite loops, how synchronized I/O and sleep influence memory visibility, and why seemingly unrelated changes sometimes make the program terminate.
The article investigates why a simple Java program that toggles a boolean flag in a child thread may never terminate when the flag is not declared volatile. It starts with a basic example where the main thread spins on while (!flag) while a worker thread sleeps 100 ms and then sets flag = true. Without volatile, the main thread may never see the update, resulting in an infinite loop.
It then examines several variations of the program, showing how seemingly unrelated modifications—adding a System.out.println statement, inserting a Thread.sleep inside the loop, marking an unrelated variable i as volatile, or changing int i to Integer i —can cause the loop to terminate. The author emphasizes that these changes are not correct fixes but rather expose underlying JVM optimizations.
The core explanation revolves around the Java Memory Model and Just‑In‑Time (JIT) compilation. When the loop is hot, the JIT may perform loop expression hoisting , caching the value of flag in a register and eliminating repeated reads, which leads to the dead loop. Introducing a synchronized I/O operation (e.g., println) or a sleep forces the JVM to insert memory barriers, preventing the hoisting and allowing the updated flag value to become visible.
The article also references Effective Java (Item 66) and discusses “active failure” where a thread does not observe another thread’s write. It cites Stack Overflow answers and Doug Lea’s book to support the claim that synchronization (even implicit via println) flushes caches and establishes a happens‑before relationship.
Additional experiments include disabling JIT with -Djava.compiler=NONE, which makes the program terminate, and using diagnostic VM options to view the generated assembly, revealing lock instructions that act as memory barriers.
In summary, the article demonstrates that:
Print statements introduce synchronization that prevents loop hoisting.
Sleep does not have synchronization semantics, but the pause gives the CPU time to refresh caches.
Marking unrelated variables as volatile or using Integer can incidentally affect cache behavior.
The proper solution is to declare the shared flag as volatile (or use explicit locking) to guarantee visibility.
Finally, the author acknowledges that many of the observed phenomena feel “mystical” and invites readers to explore the underlying JVM internals further.
public class VolatileExample {
private static boolean flag = false;
private static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
flag = true;
System.out.println("flag 被修改成 true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
while (!flag) {
i++;
}
System.out.println("程序结束, i=" + i);
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
