Question

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.

Was it helpful?

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.

OTHER TIPS

Volatile is for threading. It basically tells the threads the variable can change anytime, so anytime it wants the variable it can't rely on a cached copy it must re read it and the update it after changing it

Volatile in many senses is due to the local caching that a processor can do on a per-thread basis.

for example, lest say we have a processor with 4 threads running your java program (albeit a massively simplified example since a processor would do WAY more than just this). Lets also assume that each of those 4 main threads have access to a local cache (not to be confused with a main processor cache). So, if you just made that variable static, and all 4 threads were reading from that variable, they could all potentially put that variable in their local cache. Alright, great, access time is improved, and everything is faster. So, at the moment we have the following situation:

Thread 1: has a local copy of the variable

Thread 2: has a local copy of the variable

Thread 3: '' '' '' '' '' '' ''

Thread 4: '' '' '' '' '' '' ''

Alright, now, lets say that Thread 1 goes in and changes the ACTUAL variable, not just the copy. Thread 1 knows about the change immediately, but threads 2-4 could still be working on the value of the old, cached version of the variable since they haven't checked for any updates yet.

Now, to fix this type of situation, you can attach the 'volatile' keyword to the variable, which essentially tells it to broadcast its new value to all of the threads in the program IMMEDIATELY so that any operations on all of the threads will have the exact same value. Of course, this does incur some overhead, so something that is volatile will be a touch slower if it is modified often. Since your program is not multi-threaded (that I can tell) you'll see little to no difference using volatile or not. It's simply a trivial (and pointless) extra 'command' for the variable in single-threaded environments.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top