【发布时间】:2019-04-11 13:42:17
【问题描述】:
我有一个String 字段,它被初始化为null,但随后可能被多个线程访问。该值将在首次访问时延迟初始化为幂等计算值。
这个字段是否需要volatile 才能保证线程安全?
这是一个例子。
public class Foo {
private final String source;
private String BAR = null;
public Foo(String source) {
this.source = source;
}
private final String getBar() {
String bar = this.BAR;
if (bar == null) {
bar = calculateHashDigest(source); // e.g. an sha256 hash
this.BAR = bar;
}
return bar;
}
public static void main(String[] args) {
Foo foo = new Foo("Hello World!");
new Thread(() -> System.out.println(foo.getBar())).start();
new Thread(() -> System.out.println(foo.getBar())).start();
}
}
我以System.out.println() 为例,但我并不担心当它的调用被互锁时会发生什么。 (虽然我很确定这也是线程安全的。)
BAR 必须是volatile 吗?
我认为答案是 否,volatile 不是必需的,是它是线程安全的,主要是因为 excerpt from JLS 17.5:
final字段还允许程序员在不同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变对象,即使使用数据竞争在线程之间传递对不可变对象的引用也是如此。
因为String 的char value[] 字段确实是final。
(int hash 不是 final,但它的惰性初始化看起来也不错。)
编辑:编辑以澄清用于BAR 的值是固定 值。它的计算是幂等的,没有副作用。我不介意计算是否跨线程重复,或者BAR 由于内存缓存/可见性而成为有效的线程本地。我担心的是,如果它不是 null,那么它的值是完整的,而不是部分的。
【问题讨论】:
-
呃,字段
BAR不是final,所以final-s上的子句不适用。 (尽管 getBar() 将始终将该字段设置为相同的值;无论有多少线程同时运行它) -
如果固定值实际上是这样的字符串,您可以只使用
return "Hello World!";或类似的东西,您正在浪费时间检查null的字段。或者直接分配BAR,而不是从null值开始。如果BAR更复杂,那么延迟加载可能会有优势,但在这种情况下 幂等 或固定值无关紧要,仍然必须考虑线程安全。 -
你现在拥有的是线程安全的,只要
calculateHashDigest()是线程安全的并且只取决于它的输入(当然没有副作用),正如你所说,你不在乎BAR立即可见(它最终会变得可见,但不能说何时可见)。
标签: java concurrency thread-safety atomic