我的问题涉及到
Java中字段值的安全发布(如这里所述)
据我所知,一个字段可以安全地读取(意味着从多个线程访问将看到正确的值)如果:
>读写在同一个显示器上同步
>字段是最后的
>字段是不稳定的
如果我的理解正确,以下类不应该是线程安全的,因为初始值没有这些特征.然而,我发现很难相信,即使只能从同步方法访问,我也需要使第一个易失性.
public class Foo { private boolean needsGreeting = true; public synchronized void greet() { if (needsGreeting) { System.out.println("hello"); needsGreeting = false; } } }
我错过了什么吗?以上代码是否正确,如果是,为什么?或者在这种情况下,必须先创建volatile或者使用最终的AtomicBoolean或类似的东西,以及从synchronized方法访问它.
(只是为了澄清,我知道,如果初始值是用一个synchronized方法编写的,即使没有volatile关键字,它也是线程安全的.)
解决方法
在构造函数和方法调用的结尾之间没有发生之前的关系,因此一个线程可能开始构建实例并使引用可用,另一个线程可以获取该引用并开始调用greet( )方法. greet()中的同步并不能解决这个问题.
如果您通过着名的双重检查锁定模式发布实例,将变得更容易看到如何.如果有这样的发生关系,即使使用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()并发现needGreeting仍然是假.
Java并发实践中提到了一个例子(3.5).