我的问题涉及 Java 中字段值的安全发布(如此处讨论的 Java multi-threading & Safe Publication )。
据我了解,如果满足以下条件,则可以安全地读取一个字段(意味着来自多个线程的访问将看到正确的值):
- 读写在同一台显示器上同步
- 字段是最终的
- 字段是可变的
如果我的理解是正确的,下面的类应该不是线程安全的,因为初始值是在没有这些特征的情况下编写的。然而,我发现很难相信我需要制作 first
volatile
,即使它只能从 synchronized
方法访问。
public class Foo {
private boolean needsGreeting = true;
public synchronized void greet() {
if (needsGreeting) {
System.out.println("hello");
needsGreeting = false;
}
}
}
我错过了什么吗?上面的代码是否正确,如果正确,为什么?或者在这种情况下是否有必要使 first
volatile
或使用 final AtomicBoolean
或类似的另外从 synchronized
方法访问它。
(只是澄清一下,我知道,如果初始值是用 synchronized
方法编写的,即使没有 volatile
关键字,它也是线程安全的。 )
最佳答案
在构造函数结束和方法调用之间没有发生之前的关系,因此一个线程可以开始构造实例并使引用可用,而另一个线程可以获取该引用并开始调用部分构造的对象上的 greet() 方法。 greet() 中的同步并没有真正解决这个问题。
如果您通过著名的双重检查锁定模式发布一个实例,就会更容易看到如何操作。如果有这种happens-before关系,即使使用DCLP也应该是安全的。
public class Foo {
private boolean needsGreeting = true;
public synchronized void greet() {
if (needsGreeting) {
System.out.println("Hello.");
needsGreeting = false;
}
}
}
class FooUser {
private static Foo foo;
public static Foo getFoo() {
if (foo == null) {
synchronized (FooUser.class) {
if (foo == null) {
foo = new Foo();
}
}
}
return foo;
}
}
如果多个线程同时调用 FooUser.getFoo().greet() ,一个线程可能正在构造 Foo 实例,但另一个线程可能过早地找到一个非空的 Foo 引用,并调用 greet() 并找到needsGreeting 仍然是 false。
Java 并发实践 (3.5) 中提到了这方面的示例。
关于java - 在同步方法中读取值时的安全发布,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4032792/