gpt4 book ai didi

java - 通过引用延迟初始化的非 volatile 字符串进行访问是线程安全的吗?

转载 作者:行者123 更新时间:2023-12-01 19:40:08 25 4
gpt4 key购买 nike

我有一个 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 fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads.

并且由于 Stringchar value[] 字段确实是 final

(int hash 不是 final,但它的延迟初始化看起来也不错。)

编辑:进行编辑以澄清用于 BAR 的值是固定值。它的计算是幂等的并且没有副作用。我不介意计算是否跨线程重复,或者 BAR 是否由于内存缓存/可见性而有效地成为线程本地的。我担心的是,如果它非空,那么它的值是完整的,而不是部分的。

最佳答案

您的代码(技术上)不是线程安全的。

String 确实是一个正确实现的不可变类型,并且您关于其 final 字段的说法是正确的。但这不是线程安全问题的所在。

第一个问题是 BAR 的延迟初始化中存在竞争条件。如果两个线程同时调用 getBar(),它们都会将 BAR 视为 null,然后都尝试对其进行初始化。

第二个问题是存在内存危险。由于一个线程对 BAR 的写入与另一个线程对 BAR 的后续读取之间不存在发生之前关系,因此无法保证第二个线程线程将看到BAR的初始化值。因此,它可能会重复初始化。

请注意,在示例中所写,这两个问题不是实际的线程安全问题。您正在执行的初始化是幂等的。多次初始化 BAR 对代码的行为没有影响,因为您始终将其初始化为对同一 String 对象的引用。 (单个冗余初始化的成本太小,无需担心。)

但是,如果 BAR 是对可变对象的引用,或者初始化成本很高,那么这就是一个真正的线程安全问题。

正如 @Ravindra 所说,简单的解决方案是将 getBar 声明为 synchronized。这解决了这两个问题。

您声明 BAR 的想法解决了内存危险,但没有解决竞争条件。

<小时/>

您在问题中添加了以下内容:

Edit to clarify the value intended for BAR is a fixed value. Its calculation is idempotent and has no side-effects. I don't mind if the calculation is repeated across threads, or if BAR becomes effectively a thread-local due to memory-caching / visibility. My concern is, if it's non-null then it's value is complete and not somehow partial.

这并没有改变我上面所说的。如果该值是一个String,那么它就是一个正确实现的不可变对象(immutable对象),并且您将始终看到一个完整的值而不管任何其他内容。 JLS 引言就是这么说的!

(实际上,我掩盖了 String 使用非 final 字段来保存延迟计算的哈希码的细节。但是, String::hashCode 实现会处理这个问题。它不存在线程安全问题。如果您愿意,请自行检查。)

关于java - 通过引用延迟初始化的非 volatile 字符串进行访问是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55633948/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com