我想知道以下类是否是线程安全的:
class Example {
private Thing thing;
public setThing(Thing thing) {
this.thing = thing;
}
public use() {
thing.function();
}
}
具体来说,如果一个线程调用 setThing,而另一个线程通过 Example::use 处于 Thing::function 中,会发生什么?
例如:
Example example = new Example();
example.setThing(new Thing());
createThread(example); // create first thread
createThread(example); // create second thread
<小时/>
//Thread1
while(1) {
example.use();
}
<小时/>
//Thread2
while(1) {
sleep(3600000); //yes, i know to use a scheduled thread executor
setThing(new Thing());
}
具体来说,我想知道,当 use() 执行时调用 setThing 时,它是否会成功地继续使用旧对象,或者更新对对象的引用会以某种方式导致问题。
最佳答案
在推理特定类的线程安全性时有两点:
- 线程之间共享状态的可见性。
- 多线程通过类方法使用类对象时的安全性(保留类不变量)。
Example
类的共享状态仅由一个 Thing
对象组成。
- 从可见性的角度来看,该类不是线程安全的。一个线程的
setThing
结果不会被其他线程看到,因此它们可以使用过时的数据。 NPE 也是可以接受的,因为类初始化期间thing
的初始值为null
。 - 无法确定在没有源代码的情况下通过
use
方法访问Thing
类是否安全。但是Example
调用use
方法时没有任何同步,所以它应该是这样,否则Example
不是线程安全的。
因此示例
不是线程安全的。要修复第 1 点,如果您确实需要 setter,可以将 volatile 添加到 thing 字段,或者将其标记为 Final 并在构造函数中初始化。确保满足 2 的最简单方法是将 use
标记为 synchronized
。如果您将 setThing
标记为 synchronized
,那么您就不再需要 volatile
了。然而,还有许多其他复杂的技术可以满足第 2 点。 This伟大的书更详细地描述了这里写的所有内容。
关于设置对象引用的 Java 线程安全性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47424947/