我的理解是否正确,以下代码片段在发布标准集合方面是安全的,该集合本身不是线程安全的(在此示例中为HashSet
),因为没有一个线程在记忆障碍:
class C {
private final Set<String> strings;
C(final Set<String> strings) {
this.strings = new HashSet<>(strings);
}
void doSmthAsync() {
new Thread() {
@Override
public void run() {
for (final String s : strings) {
System.out.println(s);
}
}
}.start(); // Disregard the 2nd memory barrier caused by Thread.start()
}
}
虽然下面这个不?
class C {
private final Set<String> strings = new HashSet<>();
C(final Set<String> strings) {
this.strings.addAll(strings);
}
void doSmthAsync() {
new Thread() {
@Override
public void run() {
for (final String s : strings) {
System.out.println(s);
}
}
}.start(); // Disregard the 2nd memory barrier caused by Thread.start()
}
}
更新:上面的示例并不完全正确,因为实际上有两个内存屏障,而不是一个:
Thread.start()
相反,如果在doSmthAsync()
方法中我们将集合提供给已经启动的线程(例如来自后台线程池的线程),那么消费者线程似乎可以看到它处于状态期间的最终字段初始化(即第二种情况下的空集)。
答案 0 :(得分:1)
根据您更新的问题,addAll
中可能存在争用条件,因为在设置final
字段后会发生这种情况。这并不意味着您将看到此竞争条件,因为它的行为未定义且JVM可以自由添加内存障碍。
在启动Thread之前只读取的任何内容都是线程安全的。
顺便说一句,你的例子是两种写同一个东西的方式,所以它们就像线程安全一样。
答案 1 :(得分:1)
JSR-133 Cookbook
http://gee.cs.oswego.edu/dl/jmm/cookbook.html
最终字段的存储(在构造函数内),如果字段是引用,则此最终可以引用的任何存储都不能与对象持有的引用的后续存储(在该构造函数之外)重新排序将该字段转换为其他线程可访问的变量。例如,您无法重新排序 x.finalField = v; ......; sharedRef = x; 这就是在内联构造函数时发挥作用,其中&#34; ...&#34;跨越构造函数的逻辑结尾。您不能将构造函数中的终结存储区移动到构造函数外部的存储区之下,该存储库可能使该对象对其他线程可见。
看起来两个例子在安全发布方面是相同的。