情况如下:
问题:我是否必须使此类的所有变量都不稳定?
关注:
注意:我知道构建器模式,但由于其他几个原因我无法应用它:(
编辑: 由于我觉得Mathias和axtavt的两个答案不太匹配,我想补充一个例子:
假设我们有一个foo
类:
class Foo {
public int x=0;
}
并且两个线程正在使用它,如上所述:
// Thread 1 init the value:
Foo f = new Foo();
f.x = 5;
values.add(f); // Publication via thread-safe collection like Vector or Collections.synchronizedList(new ArrayList(...)) or ConcurrentHashMap?.
// Thread 2
if (values.size()>0){
System.out.println(values.get(0).x); // always 5 ?
}
据我了解Mathias,根据JLS,它可以在某些JVM上打印出0。据我所知,它将始终打印5。
您有什么看法?
- 问候, 德米特里
答案 0 :(得分:8)
在这种情况下,当您将对象提供给其他线程时,您需要使用安全发布惯用法,即(来自Java Concurrency in Practice):
- 从静态初始化程序初始化对象引用;
- 将对它的引用存储到易失性字段或AtomicReference中;
- 将对它的引用存储到正确构造的对象的最终字段中;或
- 将对它的引用存储到由锁定正确保护的字段中。
如果您使用安全发布,则无需声明字段volatile
。
但是,如果您不使用它,声明字段volatile
(理论上)无济于事,因为volatile
引起的内存障碍是单方面的: volatile写入可以在其后使用非易失性动作重新排序。
因此,volatile
确保在以下情况下的正确性:
class Foo {
public int x;
}
volatile Foo foo;
// Thread 1
Foo f = new Foo();
f.x = 42;
foo = f; // Safe publication via volatile reference
// Thread 2
if (foo != null)
System.out.println(foo.x); // Guaranteed to see 42
但在这种情况下不起作用:
class Foo {
public volatile int x;
}
Foo foo;
// Thread 1
Foo f = new Foo();
// Volatile doesn't prevent reordering of the following actions!!!
f.x = 42;
foo = f;
// Thread 2
if (foo != null)
System.out.println(foo.x); // NOT guaranteed to see 42,
// since f.x = 42 can happen after foo = f
从理论的角度来看,在第一个样本中存在一个传递发生在之前的关系
f.x = 42 happens before foo = f happens before read of foo.x
在第二个示例f.x = 42
中,foo.x
的读取没有按发生前关系链接,因此它们可以按任何顺序执行。
答案 1 :(得分:4)
在读取字段的线程上调用start
方法之前,您不需要声明字段volatile的值。
原因是在这种情况下,设置是在一个先发生的关系(在Java语言规范中定义)与另一个线程中的读取。
JLS的相关规则是:
但是,如果在设置字段之前启动其他线程,则必须声明字段volatile。 JLS不允许您假设线程在第一次读取它之前不会缓存该值,即使在特定版本的JVM上可能就是这种情况。
答案 2 :(得分:0)
为了充分了解我正在阅读的内容,我一直在阅读有关Java内存模型(JMM)的内容。有关JMM的有用介绍可以在Java Conurrency in Practice中找到。
我认为这个问题的答案是:是的,在给出使对象volatile的成员不是必需的示例中。但是,这种实现方式相当脆弱,因为这种保证取决于完成事情的确切ORDER以及Container的Thread-Safety。构建器模式将是一个更好的选择。
为什么保证:
线程安全容器的add方法必须使用一些同步构造,如volatile read / write,lock或synchronized()。这保证了两件事:
发布后不会修改对象。
但是,如果容器不是线程安全的,或者由于某人不知道该模式或者在发布后意外更改了对象而更改了事物顺序,那么就不再有任何保证了。因此,遵循Builder模式,可以通过谷歌AutoValue或Freebuilder生成更安全。
这篇关于这个主题的文章也很不错: http://tutorials.jenkov.com/java-concurrency/volatile.html