Search code examples
javamultithreadingvolatilestatic-variablesjava-memory-model

Why no volatile?


I have a discussion with a colleague about this code:

public final class ShutdownHookRegistration {

/**
 * Global shutdown flag
 */
private static boolean isServerShutdown = false;

private ShutdownHookRegistration() {
    // empty
}

/**
 * Returns the current value of the global shutdown flag
 *
 * @return
 */
public static boolean isServerShutdown() {
    return isServerShutdown;
}

/**
 * Registration if shutdown hooks
 */
public static void registerShutdownHooks() {
    /**
     * 1. Shutdown hook to set the shutdown flag
     */
    Runtime.getRuntime().addShutdownHook(setGlobalShutdownFlag());
}

/**
 * Sets the global static is shutdown flag which can be checked by other processes.
 *
 * @return
 */
private static Thread setGlobalShutdownFlag() {
    return new Thread() {

        @Override
        public void run() {
            isServerShutdown = true;
            System.out.println(Thread.currentThread().getName() + ":shutdown set");
        }
    };
}

public static void main(String[] args) throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " Flag set:" + ShutdownHookRegistration.isServerShutdown);
    Thread t1 = ShutdownHookRegistration.setGlobalShutdownFlag();
    Thread t2 = new Thread() {

        public void run() {
            while (!ShutdownHookRegistration.isServerShutdown) {
                System.out.println(Thread.currentThread().getName() + " Flag set:" + ShutdownHookRegistration.isServerShutdown);
            }
        }
    };
    t2.start();
    t1.start();
}

Output:

Thread-1 Flag set:false
Thread-1 Flag set:false
Thread-1 Flag set:false
Thread-1 Flag set:false
[..]
Thread-0:shutdown set

I thought without volatile this code would run in an infinite loop, but somehow it will always terminate.

Can someone explain why here no volatile is necessary?


Solution

  • In short there is two reasons, your loop has a memory barrier and even if it didn't it's not run long enough to be optimised/compiled in a matter which need volatile.

    The key is here

    while (!ShutdownHookRegistration.isServerShutdown) {
        System.out.println(Thread.currentThread().getName() + " Flag set:" + ShutdownHookRegistration.isServerShutdown);
    }
    

    System.out.println is synchronized which means there is a read/write barrier in each iteration.

    // from the java 6 source
    public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }
    

    In the x64 JVM, once you lock one object you place a memory barrier which protects all memory accesses.

    Additionally, this slows down your code by 10,000x or more, so that it doesn't run long enough to get compiled (and optimised in a matter which required volatile) It would take looping in the order of 10,000 times before this code is compiled.