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.
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With