非线程安全标准集合的安全发布
Safe publication of a not-threadsafe standard collection
我的理解是否正确,以下代码片段在发布本质上不是线程安全的标准集合方面是安全的(本例中为 HashSet
),因为 none线程在内存屏障后修改集合:
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()
}
}
更新:上面的例子并不完全正确,因为实际上有两个内存屏障,而不是一个:
- final 字段的初始化
Thread.start()
如果相反,在 doSmthAsync()
方法中,我们将集合提供给一个已经启动的线程(例如,来自后台线程池的线程),似乎消费者线程可能会看到它状态中的最终字段在初始化期间有(即在第二种情况下为空集)。
根据您更新的问题,addAll
中可能存在竞争条件,因为这发生在设置了 final
字段之后。这并不意味着您会看到这种竞争条件,因为它的行为是未定义的,并且 JVM 可以自由添加内存屏障。
在启动线程之前只读的任何内容都是线程安全的。
顺便说一句,您的示例是编写同一事物的两种方式,因此它们彼此一样线程安全。
JSR-133 食谱
http://gee.cs.oswego.edu/dl/jmm/cookbook.html
A store of a final field (inside a constructor) and, if the field is a reference, any store that this final can reference, cannot be reordered with a subsequent store (outside that constructor) of the reference to the object holding that field into a variable accessible to other threads. For example, you cannot reorder
x.finalField = v; ... ; sharedRef = x;
This comes into play for example when inlining constructors, where "..." spans the logical end of the constructor. You cannot move stores of finals within constructors down below a store outside of the constructor that might make the object visible to other threads.
看起来两个例子在安全发布方面是等效的。
我的理解是否正确,以下代码片段在发布本质上不是线程安全的标准集合方面是安全的(本例中为 HashSet
),因为 none线程在内存屏障后修改集合:
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()
}
}
更新:上面的例子并不完全正确,因为实际上有两个内存屏障,而不是一个:
- final 字段的初始化
Thread.start()
如果相反,在 doSmthAsync()
方法中,我们将集合提供给一个已经启动的线程(例如,来自后台线程池的线程),似乎消费者线程可能会看到它状态中的最终字段在初始化期间有(即在第二种情况下为空集)。
根据您更新的问题,addAll
中可能存在竞争条件,因为这发生在设置了 final
字段之后。这并不意味着您会看到这种竞争条件,因为它的行为是未定义的,并且 JVM 可以自由添加内存屏障。
在启动线程之前只读的任何内容都是线程安全的。
顺便说一句,您的示例是编写同一事物的两种方式,因此它们彼此一样线程安全。
JSR-133 食谱
http://gee.cs.oswego.edu/dl/jmm/cookbook.html
A store of a final field (inside a constructor) and, if the field is a reference, any store that this final can reference, cannot be reordered with a subsequent store (outside that constructor) of the reference to the object holding that field into a variable accessible to other threads. For example, you cannot reorder x.finalField = v; ... ; sharedRef = x; This comes into play for example when inlining constructors, where "..." spans the logical end of the constructor. You cannot move stores of finals within constructors down below a store outside of the constructor that might make the object visible to other threads.
看起来两个例子在安全发布方面是等效的。