Search code examples
javavolatile

Why using "volatile" does not show any difference here?


I am learning the usage of volatile in Java. Here is a sample code I read from many articles:

   static volatile boolean shutdownRequested = false;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

I try this on my machine with and without "volatile", but they show no difference: they can both shutdown. So what's wrong? Is there anything wrong with my code, or does it depend on the version of the Java compiler?

Addition: in many articles, they say this program without "volatile" will not successfully shutdown because this loop while (!shutdownRequested) will be optimized to while(true) by Java compiler if the value of the variable shutdownRequested is not changed inside the loop. But the result of my experiment does not stand for that.


Solution

  • I assume you mean you have a setup something like this:

    final Worker theWorker = new Worker(); // the object you show code for
    
    new Thread(new Runnable() {
        public void run() {
            theWorker.doWork();
        }
    }.start();
    
    try {
        Thread.sleep(1000L);
    } catch(InterruptedException ie) {}
    
    theWorker.shutdown();
    

    And what you found is that the shutdown works even without volatile.

    It's typically the case that this is true: non-volatile writes may be seen eventually. The important thing is that there is not a guarantee this needs to be the case and you can't rely on it. In practical use you may also find there is a small but noticeable delay without volatile.

    Volatile provides a guarantee that writes are seen immediately.

    Here's some code that might reproduce the HotSpot optimization we discussed in the comments:

    public class HotSpotTest {
        static long count;
        static boolean shouldContinue = true;
    
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                public void run() {
                    while(shouldContinue) {
                        count++;
                    }
                }
            });
            t.start();
    
            do {
                try {
                    Thread.sleep(1000L);
                } catch(InterruptedException ie) {}
            } while(count < 999999L);
    
            shouldContinue = false;
            System.out.println(
                "stopping at " + count + " iterations"
            );
    
            try {
                t.join();
            } catch(InterruptedException ie) {}
        }
    }
    

    Here's a quick review if you don't know what HotSpot is: HotSpot is the Java just-in-time compiler. After some fragment of code has run a certain number of times (from memory, 1000 for desktop JVM, 3000 for server JVM), HotSpot takes the Java bytecode, optimizes it, and compiles it to native assembly. HotSpot is one of the reasons Java is so lightning fast. In my experience, code recompiled by HotSpot can be easily 10x faster. HotSpot is also much more aggressive about optimization than a regular Java compiler (like javac or others made by IDE vendors).

    So what I found is the join just hangs forever if you let the loop run long enough first. Note that count is not volatile by design. Making count volatile seems to foil the optimization.

    From the perspective of the Java memory model it makes sense that as long as there is absolutely no memory synchronization HotSpot is allowed to do this. HotSpot knows there's no reason the update needs to be seen so it doesn't bother checking.

    I didn't print the HotSpot assembly since that requires some JDK software I don't have installed but I'm sure if you did, you'd find the same thing the link you provided recalls. HotSpot does indeed seem to optimize while(shouldContinue) to while(true). Running the program with the -Xint option to turn HotSpot off results in the update being seen as well which also points to HotSpot as the culprit.

    So, again, it just goes to show you can't rely on a non-volatile read.