Java thread visibility issues can produce unexpected behavior when shared variables are accessed across threads without proper synchronization. This article examines three peculiar cases where programs behavee differently than expected due to JVM optimizations and memory visibility effects.
Basic Visibility Problem
Consider this simple program where a background thread modifies a shared flag after a delay:
public class VisibilityExample {
private static boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
try {
Thread.sleep(100);
flag = true;
System.out.println("Flag set to true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
while (!flag) { /* Empty loop */ }
System.out.println("Loop exited");
}
}
Without volatile on flag, this program may run indefinitely due to visibility issues. The JVM's just-in-time (JIT) compiler may optimize the loop by hoisting the flag check outside the loop.
Case 1: Adding Output Statements
Adding a print statement inside the loop unexpectedly makes the program terminate:
while (!flag) {
System.out.println("Looping"); // Program now terminates
}
This occurs because System.out.println contains synchronized blocks that effect memory visibility. The JVM becomes more conservative about optimizations when synchronization is presant.
Case 2: Adding Sleep
Inserting a sleep call also makes the program terminate:
while (!flag) {
try {
Thread.sleep(10); // Program now terminates
} catch (InterruptedException e) {
e.printStackTrace();
}
}
While Thread.sleep has no synchronization semantics, it may cause the JVM to reload values from memory more frequently due to reduced CPU utilization.
Case 3: Integer Operations
Changing the counter variable to Integer can also affect visibility:
private static Integer counter = 0;
while (!flag) {
counter++; // Program may terminate
}
The boxing/unboxing operations involved in Integer arithmetic may introduce memory barriers or affect JIT optimization patterns.
Key Takeaways
- The JVM performs aggressive optimizations that can affect visibility of shared variables
- Certain operations (output, sleep, boxing) can indirectly affect visibility
- The only reliable solution is proper use of
volatileor other synchronization - Behavior may vary across JVM implementations and hardware
These examples demonstrate why understanding Java's memory model is crucial for writing correct concurrent programs.