javamultithreadingsynchronizednon-volatile

Why this code finishes if getter is marked as synchronized?


Why this code successfully finishes when method get() is marked as synchronized despite the fact that field value is not volatile? Without synchronized it runs indefinitely on my machine (as expected).

public class MtApp {

    private int value;

    /*synchronized*/ int get() {
        return value;
    }

    void set(int value) {
        this.value = value;
    }

    public static void main(String[] args) throws Exception {
        new MtApp().run();
    }

    private void run() throws Exception {
        Runnable r = () -> {
            while (get() == 0) ;
        };
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(10);
        set(5);
        thread.join();
    }
}

Solution

  • @Andy Turner is partly correct.

    The addition of the synchronized on the get() method is affecting the memory visibility requirements, and causing the (JIT) compiler to generate different code.

    However, strictly speaking there needs to be a happens before relationship connecting the set(...) call and the get() call. That means that the set method should be synchronized as well as get (if you are going to do it this way!).

    In short, the version of your code that you observed to be working is NOT guaranteed to work on all platforms, and in all circumstances. In fact, you got lucky!


    Reading between the lines, it seems that you are trying to work out how Java's memory model works by experimentation. This is not a good idea. The problem is that you are trying to reverse engineer an immensely complicated black box without enough "input parameters"1 for you to vary to cover all potential aspects of the black boxes behavior.

    As a result the "learning by experiment" approach is liable to leave you with an incomplete or mistaken understanding.

    If you want a complete and accurate understanding, you should start by reading about the Java Memory Model in a good text book ... or the JLS itself. By all means, use experimentation to try to confirm your understanding, but you do need to be aware that the JMM specifies (guarantees) only what happens if you do the right thing. If you do the wrong thing, your code may work anyway ... depending on all sorts of factors. Hence, it is often difficult to get experimental confirmation that a particular way of doing things is either correct or incorrect2.

    1 - Some the parameters you would need don't actually exist. For example, the one that allows you to run Java N for N > 12, or the one that allows you to run on hardware that you don't have access ... or that doesn't yet exist.

    2 - As is illustrated by your example. You are getting the "right" answer, even though the code is wrong.