Why can System.out.println be used to exit while loop

Summary

A common issue in multi-threaded Java involves a background thread failing to observe updates to a shared boolean flag, leading to an infinite loop due to memory visibility problems. Surprisingly, inserting System.out.println() or Thread.sleep() within the loop can cause it to terminate as expected. This occurs because the JVM may opportunistically synchronize variables from main memory during blocking operations, providing a window for the flag's updated value to become visible. However, this behavior is not guaranteed and differs from the explicit memory barrier provided by the volatile keyword. Developers should rely on volatile for robust memory visibility rather than incidental side effects of other operations.

Let's first take a look at one simple Java thread code snippet which is supposed to exit the while loop after the first loop run.

public class StopThread {
 private static boolean stopRequested;
 public static void main(String[] args) throws InterruptedException {
 Thread backgroundThread = new Thread(new Runnable() {
 @Override
 public void run() {
 int i = 0;
 while (!stopRequested) {
 i++;
 }
 }
 });
 backgroundThread.start();
 TimeUnit.SECONDS.sleep(1);
 stopRequested = true;
 }
}

But the truth is that the loop may not exit and keep running. The reason is that the updated stopRequested value will be in the main memory while the value of stopRequested for the thread is still in local stack which is not synced up with main memory. In this case, volatile may be used to enforce the sync up between main memory and the thread so that the thread always gets the updated value. 

However, the topic of this post is not about volatile. It's another question. Can the while loop be exited with other methods?

Let's take a look at below code.

public class StopThread {
 private static boolean stopRequested;
 public static void main(String[] args) throws InterruptedException {
 Thread backgroundThread = new Thread(new Runnable() {
 @Override
 public void run() {
 int i = 0;
 while (!stopRequested) {
 i++;
 System.out.println(""+i);
 }
 }
 });
 backgroundThread.start(); 
 TimeUnit.SECONDS.sleep(1);
 stopRequested = true;
 }
}

The only change is that System.out.println(""+i); added after i++;, this simple change will make the while loop exit after one loop. Some people may think that it's because there is synchronized keyword in System.out.println() source code which would sync up the value of stopRequested. But this understanding is incorrect since synchronized is only for the variables inside the synchronized block. The stopRequested variable is outside the synchronized block.

What happens indeed would be that JVM will try its best to ensure the visibility of variables from main memory to other memory areas. i.e, JVM would try to update the variable as long as there are free CPU clocks. The difference of this behavior and volatile is that volatile will force to ensure the visibility of the variable while the default behavior of JVM is it tries its best to do this but not guaranteed. That's why volatile keyword is needed if want an anticipated behavior.

In this example, System.out.println() has synchronized keyword in its implementation which may block the execution while waiting for other threads execution, in the meantime, CPU can be try to sync up the variables in memory. Though this is not guaranteed to be updated. After knowing this, it seems other methods like Thread.sleep() can also make the while loop exit.

public class StopThread {
 private static boolean stopRequested;
 public static void main(String[] args) throws InterruptedException {
 Thread backgroundThread = new Thread(new Runnable() {
 @Override
 public void run() {
 int i = 0;
 while (!stopRequested) {
 try {
 TimeUnit.SECONDS.sleep(3);
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 System.out.println(i);
 }
 }
 });
 backgroundThread.start();
 TimeUnit.SECONDS.sleep(1);
 stopRequested = true;
 }
}

Note this post is not to tell you that you should use these methods to exit the while loop. it's just to explore the behavior of JVM and the language itself so that you would understand better on what's happening when something unexpected happens. The correct way is still to use volatile whenever you can.

Reference: https://www.toutiao.com/i6636959373736280590/

JAVA THREAD VOLATILE

  RELATED

  COMMENTS

0

No comment for this article.