以下代码有时会在我的Windows-PC和Mac上打印"valueWrapper.isZero()",它们都在服务器模式下运行它们的JVM.确定发生这种情况是因为值字段在ValueWrapper类中不是最终的,因此某些线程可能会看到过时值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(); } }
但是下面的修改呢,这里我使用了一个局部最终变量:
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(); } }
看起来现在没有线程看到过时的值为0. 但这是否由JMM保证? 在规范中简要介绍并不能说服我.
我要谈的是格雷没有提出的观点,但我会接受他的观点,因为他的回答很明确
以下代码有时会在Windows-PC和Mac上以服务器模式运行JVM的情况下打印“ valueWrapper.isZero()”。看起来现在没有线程看到过时的值0。由JMM?规格中的简短外观并不能说服我。
您valueWrapper.isZero()
有时会看到返回true 的原因,是因为valueWrapper
在start
调用之后和run
进入布尔测试之前发生了变化。如果您仅创建了一个实例,那么它将永远不会为零(如Gray所述)。
之所以一直final ValueWrapper valueWrapper = new ValueWrapper(randomInt(10, 1024));
有效,是因为该字段是线程(和方法)本地的,而本地对象和匿名内部类的语义是将原始引用复制到类实例中。
看起来现在没有线程看到过时的值为0.但这是否由JMM保证?在规范中简要介绍并不能说服我.
这是有保证的,但不是因为final
.在分叉线程时有一个先发生的保证.在开始新线程之前在分叉线程中完成的任何内存操作都保证被新线程视为完全构造和发布.引用JLS 17.4.4 - 同步顺序:
启动线程的操作与其启动的线程中的第一个操作同步.
这与final
我们谈论对象构建和发布时的领域不同.如果一个字段是final
则可以保证当构造完成并对象被发布到多个线程进行正确的初始化.在您的情况下,final
由于匿名类,这是必要的.如果你没有使用匿名类,那么你可以删除final
你的ValueWrapper
,你的对象仍然可以保证完全构建,因为上述.
仅供参考,请参阅此处获取final
字段信息:Java并发:是最终字段(在构造函数中初始化)是否是线程安全的?