Skip to content
Advertisement

Thread.sleep behaviour with non-volatile boolean variable

According to the JLS 17 specification section 17.3:

For example, in the following (broken) code fragment, assume that this.done is a non-volatile boolean field:

while (!this.done)
    Thread.sleep(1000);

The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done

I have tried to simulate this following example: 2 threads concurrently access the same boolean variable, the first thread using the shared boolean in while loop, and the second thread update the boolean value.

1.Code without Thread.sleep() inside the first thread:

public boolean done;
public void performTest() throws InterruptedException {
    done = false;
    new Thread(() -> {
        System.out.println("Running Thread 1...");
        int count = 0;
        while (!done) {
            count++;
        }
        System.out.println("Exiting thread...");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Thread 2 setting done to true");
        done = true;
    }).start();
}

-> This code would never terminated, because the done variable not declared as volatile

2.Now change the code to include Thread.sleep() inside while loop as mentioned in the JLS

public boolean done;

public void performTest() throws InterruptedException {
    done = false;
    new Thread(() -> {
        System.out.println("Running Thread 1...");
        int count = 0;
        while (!done) {
            count++;
            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Exiting thread...");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
        System.out.println("Thread 2 setting done to true");
        done = true;
    }).start();
}

-> Now it successfully exists in the first thread.

So i am confused between this example and the JLS mentioned. Not sure what i am missing here.

Note: i also noticed that Venkat’s also mentioned this example in one of his videos, and there is a blog post that explained this behavior, and it looks like there is something related to JIT optimization. What is really concerned me here is that this example is not like what is described in the JLS.

Advertisement

Answer

The reason that the code is broken in your example is because the JVM is free to use a cached version of done so that your loop would never end. When you have ‘Thread.sleep()’ in there, it is unlikely to happen, but it is still a possiblity. That means, you write some code and test it and it works great. Then you change an environment, or change a JVM and suddenly it is broken.

This is a poor benchmark, but it gives an idea.

public class VolatileTest implements Runnable{
  boolean done = false;
  public void run(){
    long count = 0;
    long start = System.nanoTime();
    long end = Integer.MAX_VALUE;
    while(!done){
        count++;   
        if(count == end){
            break;
        }     
        //try{ Thread.sleep(0); } catch (Exception e){ break;}
    }
    System.out.println( System.nanoTime() - start + " with " + count + " iterations");
  }
  public static void main(String[] args) throws Exception{
  
      VolatileTest vt = new VolatileTest();
      new Thread(vt).start();
      Thread.sleep(500);
      vt.done = true;
  }
}

Now there are 3 cases. 1st as written without any sleep/volatile.

650503733 with 2147483647 iterations

It took 650ms to complete Integer.MAX_VALUE iterations. note sometimes this finishes faster than the 500ms I wait.

2nd case, volatile done.

499923823 with 1091070867 iterations

Now it never completes before vt.done is set to true.

3rd case. non-volatile with Thread.sleep

499905166 with 3031374 iterations

With the volatile version is 300 times faster than the Thread.sleep version. The non-volatile version is more intermittent in how fast it is but it is the fastest. I suspect due to when the JIT decides to cache done it gets a speed boost so to speak.

I’m not sure how to verify when it decides to cache the done variable, but I think that why JMH is necessary for these types of micro benchmarks.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement