Question

The following code sometimes prints "valueWrapper.isZero()" on my Windows-PC and a Mac, both running their JVM in server mode. Ok this happens because the value field isn't final in the ValueWrapper class, so its possible that some thread sees the stale value 0.

public class ConcurrencyApp {
    private final Random rand = new Random(System.currentTimeMillis());
    private ValueWrapper valueWrapper;

    private static class ValueWrapper {
       private int value;

        public ValueWrapper(int value) {
            this.value = value;
        }

        public boolean isZero() {
            return value == 0;
        }
    }

    private void go() {
        while (true) {
            valueWrapper = new ValueWrapper(randomInt(10, 1024));
            Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    if (valueWrapper.isZero()) {
                        System.out.println("valueWrapper.isZero()");
                    }
                }
            });
            thread.start();
        }
    }

    private int randomInt(int min, int max) {
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }

    public static void printVMInfos() {
        String vmName = System.getProperty("java.vm.name");
        System.out.println("vm name: " + vmName);
        int cores = Runtime.getRuntime().availableProcessors();
        System.out.println("available cores: " + cores);
    }

    public static void main(String[] args) {
        ConcurrencyApp app = new ConcurrencyApp();
        printVMInfos();
        app.go();
    }
}

But what about the following modification, here i used a local final variable:

private void go() {
    while (true) {
        final ValueWrapper valueWrapper = new ValueWrapper(randomInt(10, 1024));
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                if (valueWrapper.isZero()) {
                    System.out.println("valueWrapper.isZero()");
                }
            }
        });

        thread.start();
    }
}

It looks like that now no thread sees a stale value of 0. But is this guaranteed by the JMM? A brief look in the spec doesn't convinced me.

Était-ce utile?

La solution 2

I am addressing one point Gray didn't, but I would accept his, as his answer is spot on

The following code sometimes prints "valueWrapper.isZero()" on my Windows-PC and a Mac, both running their JVM in server mode.... It looks like that now no thread sees a stale value of 0. But is this guaranteed by the JMM? A brief look in the spec doesn't convinced me.

The reason you are seeing valueWrapper.isZero() returning true sometimes because valueWrapper is changing after the start is invoked and before run gets to the boolean test. If you only have one instance created it will always not be zero as Gray mentioned.


The reason final ValueWrapper valueWrapper = new ValueWrapper(randomInt(10, 1024)); works all the time is because the field is thread (and method) local and the semantics for a local object and anonymous inner classes is to copy the original reference into the class instance.

Autres conseils

It looks like that now no thread sees a stale value of 0. But is this guaranteed by the JMM? A brief look in the spec doesn't convinced me.

It is guaranteed but not because of the final. There is a happens-before guarantee when you fork a thread. Any memory operations done in the forking thread before you start a new thread are guaranteed to be seen by the new thread as fully constructed and published. To quote from JLS 17.4.4 - Synchronization Order:

An action that starts a thread synchronizes-with the first action in the thread it starts.

This is different from a final field when we are talking about object construction and publishing. If a field is final then it is guaranteed to be properly initialized when the constructor finishes and the object is published to multiple threads. In your case, the final is necessary because of the anonymous class. If you weren't using an anonymous class and then you could remove the final on your ValueWrapper, your object would still be guaranteed to be fully constructed because of the above.

FYI, see here for final field info: Java concurrency: is final field (initialized in constructor) thread-safe?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top