将String类型引用标记为Volatile安全吗?

7
我读过一些帖子和文章,说我们不应该将Java对象声明为volatile,因为结果只有引用变成了volatile。以下是一些例子:

link-1 link-2 link-3

Sonar建议“非原始字段不应该是'volatile'”,然而,它也建议所描述的问题涉及可变对象,“同样,将可变对象字段标记为volatile意味着对象引用是volatile的,但对象本身不是”。

我的问题是:将Java字符串声明为volatile是否安全?


2
你的标题不够清晰明了。请重新撰写以概括你具体的技术问题。 - Basil Bourque
“safe”这个词并不是很清楚你的意思。无论字段是否指向对象,volatile关键字都会按照其规定的方式执行。例如,它不会爆炸。而且,由于String实例是不可变的,所以它们本身不会因为被多个线程使用而出现故障,无论你对它们做什么或者是否使用volatile字段来操作它们。但这并不意味着volatile的作用就足以使应用程序按照您的意愿运行。在所有地方添加volatile并不能神奇地使应用程序具有线程安全性。 - Boann
抱歉,这个标题是由管理员设置的,不是我设置的。 - jaros
2个回答

5
因为 String 对象是不可变的,所以像 =+= 这样的运算符只会修改引用。因此,对于 String,volatile 是安全的,因为它适用于引用本身。这同样适用于其他不可变对象,就像基元类型一样。
澄清:
即使在 volatile 的 String 上,+= 本身也不是线程安全的,因为它不是原子性的,包含一个读操作和一个写操作。如果在读和写之间对 String 对象进行了更改,则可能导致意外结果。虽然生成的 String 仍然有效,但它可能具有意外的值。特别地,某些更改可能会“覆盖”其他更改。例如,如果您有一个值为 "Stack "String,一个线程尝试添加 "Overflow",而另一个线程尝试添加 "Exchange",则有可能只应用一个更改。这同样适用于基元类型。如果您感兴趣,可以在 这里 找到有关这个特定问题的更多详细信息(主要是在基元类型的上下文中)。

3
好的,我会尽力进行翻译。如果要对 += 添加同步功能,则最简单的方法是使用 AtomicReference 进行同步,这种情况下就不需要使用 volatile - Bohemian
1
那么 volatile 对于 String 是安全的,但是在使用 += 运算符时就不安全了?我实际上喜欢 @Bohemian 建议的 AtomicReference 的想法。听起来比 volatile 本身不那么令人困惑。 - jaros
1
@jaros 普通的字符串拼接是非线程安全的操作,因为在写入新值之前另一个线程可能会修改该值/引用。AtomicReference 提供了 getAndUpdate() 方法,以线程安全的方式应用更新。 - Bohemian
1
你的“澄清”让事情变得更加混乱。如果我们所说的“线程安全”是指String对象本身不会内部故障,那么+=是线程安全的。因为它们是不可变的,所以没有任何东西可以“影响”它们。+=操作不是原子性的,因此如果多个线程同时使用+=,则可能会覆盖一些附加的文本,但您始终会看到某些 功能正常的String对象。 - Boann
@Boann 我的意思是 += 在线程安全方面存在问题,竞态条件可能会导致意外结果。虽然 String 对象仍然可用,但如果涉及多个线程,则其值可能与预期完全不同。我将编辑我的答案以更清楚地表达这一点。 - Evan Bailey

0
Java String是final类,不可变且线程安全。
String没有中间状态,在多线程情况下不会与locksynchronize混淆。 没有必要这样做。

2
你是什么意思? 是的,字符串在其不变的内部状态上是线程安全的。那引用呢?如果你有一个实例变量private String ref =“some-string”;现在很可能会有一个线程修改引用ref=“some-other-string”。此外,答案必须详细说明并添加超过注释的价值。单行注释也可以成为注释的一部分。 - nits.kk
2
如果一个程序中有 private String ref = "some-string"; 这样的语句和一个方法 public void set(String s){ ref = s;},现在线程 T1 调用了 set("Abc"),线程 T2 调用了 set("123"),同时线程 T3 在一个 while 循环中读取该字符串,你认为会出现什么行为? - nits.kk
2
此外,请阅读我的第一条评论。这不是关于保护内部状态,而是关于引用变量。除非将引用标记为static final,否则您没有将String类型的引用视为线程安全。String对象是线程安全的,而其非静态final引用则不是。 - nits.kk
2
volatile 保证任何读取操作都来自内存而不是线程缓存。当你读取任何引用时,其值是否可以更改取决于 volatile 的使用方式。 - nits.kk
2
你所说的检查 a 的值是什么意思?你确定在没有任何同步构造和未声明为 volatile 的情况下,当你检查 a 的值时,a 的值是从内存中读取而不是线程本地缓存吗? - nits.kk
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接