Exploring Thread Visibility Issues in Java: Output Statements, Sleep, and Integer Operations

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

  1. The JVM performs aggressive optimizations that can affect visibility of shared variables
  2. Certain operations (output, sleep, boxing) can indirectly affect visibility
  3. The only reliable solution is proper use of volatile or other synchronization
  4. Behavior may vary across JVM implementations and hardware

These examples demonstrate why understanding Java's memory model is crucial for writing correct concurrent programs.

Tags: java Concurrency thread-safety JVM memory-model

Posted on Mon, 29 Jun 2026 17:28:12 +0000 by Fantast