Search code examples
javasynchronizedvolatilejava-memory-model

How synchronized and volatile work in Java memory model?


In the "Effective Java" book:

// Broken! - How long would you expect this program to run?
public class StopThread {

    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

the backgroundThread will not stop after one second. Because hoisting, the optimization in JVM, HotSpot server VM does.

You can view this in the following topic:
Why HotSpot will optimize the following using hoisting?.

The optimization goes like this:

if (!done)
    while (true)
        i++;

there are two ways to fix the problem.

1. Use volatile

private static volatile boolean stopRequested;

The function of volatile is
- forbid hoisting
- it guarantees that any thread that reads the field will see the most recently written value

2. Use synchronized

public class StopThread {

    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args)
                throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested())
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

The above code is right in the Effective Java book, its equivalent that use volatile to decorate the stopRequested.

private static boolean stopRequested() {
    return stopRequested;
}

If this method omit the synchronized keyword, this program isn't working well.
I think that this change cause the hoisting when the method omit the synchronized keyword.
Is that right?


Solution

  • To clearly understand why this happens you need to know something about what happens at a deeper level. (This is basically an explanation of what is called a happens-before relationship, with a language that I hope is more understable for the reader).

    Normally variables are present in the RAM memory. When a thread needs to use them it take them from the RAM and put them in cache so it can access them as fast as possible until needed.

    Using volatile force a thread to read and write variables directly from RAM memory. So when many thread are using the same volatile variable all them see the last version that is present in RAM memory and not a possible old copy in cache.

    When a thread enter a synchronized block it needs to take control of a monitor variable. All other threads wait until the first thread exit from the synchronized block. To ensure that all thread can see the same modifications all variables used in a synchronized block are read and written directly from the RAM memory instead from a cache copy.

    So if you try to read the variable stopRequested without a synchronized method or without a volatile keyword you can read a possible old copy of it that is present in the cache.

    To solve this you need to be sure that:

    • all threads are using volatile variables
    • or all thread that access those variable are using a synchronized block.

    Using the method

    private static boolean stopRequested() {
       return stopRequested;
    }
    

    without a synchronized keyword and when stopRequested is not volatile means that you can read the value of stopRequested from a cache copy that is invalid.