0

I have the following class:

public class ThreadStopTest {

private static boolean enabled = true;

private static int milliseconds = 5;

public static void main(String[] args) {
    new Thread(ThreadStopTest::process).start();
    sleepMillSeconds(milliseconds);
    disableProcessing();
}

private static void disableProcessing() {
    enabled = false;
    System.err.println("Processing disable called");
}

private static boolean isEnabled() {
    return enabled;
}

private static void process()  {
    boolean printed = false;
    while (isEnabled()) {
        if(!printed) {
            System.err.println("Processing started");
            printed = true;
        }
    }
    System.err.println("Processing finished");
}

private static void sleepMillSeconds(int mills) {
    try {
        Thread.sleep(mills);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

In case if milliseconds > 5, this class execution hangs and never stops, but if milliseconds <=5, then the program runs and completes successfully.

Adding a volatile modifier to the enabled parameter solves the problem, but I'd like to know what is the reason for this weird behavior. Did anyone get ideas?

  • 1
    If you have a long running or blocking method, sometimes it may be reasonable to use standard java way to stop execution - to declare that the method throws InterruptedException and to check Thread.currentThread().isInterrupted() to throw an InterruptedException in your loop, instead of checking a synthetic value like your isEnabled. – AnatolyG Aug 13 '21 at 13:23

1 Answers1

2
private static boolean enabled = true;

The thread which runs the process() method has no reason to believe this value will be changed by another thread, so the JVM may decide that it won't bother to read its value more than once (it can read it more than once, it's just not guaranteed to).

By making it volatile, you indicate that it needs to read the current value on every evaluation of isEnabled():

private static volatile boolean enabled = true;

More formally, volatile creates a happens-before relationship between the write in disableProcessing() and the read in isEnabled().

It follows from the above definitions that:

  • ...
  • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
  • ...

Why does it matter if you sleep for more than 5ms or not? Who knows, it could be for a whole number of JVM-specific reasons. It's perhaps because of the way Thread.sleep method works (emphasis mine):

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

If you sleep for 5ms or less, it's perhaps the case that it actually doesn't sleep at all, or the sleep is otherwise shorter than the thread scheduling time, so you've updated the enabled flag before it's first read in the other thread.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Thank you for the explanation regarding volatile keyword. I'd like to know this specific JVM reason, what is impacted by Thread sleep and why its working without volatile if Thread doesn't sleep. –  Aug 13 '21 at 13:24
  • It's not worth trying to find a specific reason why the sleep delay seems to make it work: unless you create a happens-before relationship between the write and the read, your code has no guarantee of correct functioning. – Andy Turner Aug 13 '21 at 13:28
  • 1
    The reasons are JIT compilator optimizations. Your process method calls for isEnabled() method a lot of times. That's why JIT compilator could replace it with some constant. Try to run your test with -XX:-Inline or -XX:CompileCommand=exclude, ThreadStopTest .isEnabled VM options. That should turn off JIT optimizations for isEnabled method. – Serhii Soboliev Aug 17 '21 at 17:53