在同步方法中读取值时安全发布

时间:2010-10-27 11:39:26

标签: java multithreading thread-safety

我的问题涉及Java中字段值的安全发布(正如此处Java multi-threading & Safe Publication所述)。

据我了解,如果出现以下情况,可以安全地读取字段(意味着从多个线程访问将看到正确的值):

  • 读取和写入在同一台监视器上同步
  • field is final
  • 字段不稳定

如果我的理解是正确的,下面的类应不是线程安全的,因为初始值是在没有这些特性的情况下编写的。但是,我发现很难相信我需要制作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关键字,它也会是线程安全的。)

2 个答案:

答案 0 :(得分:5)

在构造函数结束和方法调用之间没有发生之前的关系,因此一个线程可以开始构造实例并使引用可用,并且另一个线程可以获取该引用并开始调用部分构造的对象上的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 Concurrency in Practice(3.5)中提到了一个例子。

答案 1 :(得分:3)

严格来说,我认为可以安全地假设needsGreeting在调用greet时设置为true。

为了实现这一点,必须在初始写入(在构造对象时发生)和第一次读取(在greet - 方法中)之间发生关系。但是,JLS中的Chapter 17 Threads and Locks表示以下关于发生前( hb )约束的内容:

  

17.4.5在订单之前发生   可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并且在第二个之前被命令。

     

如果我们有两个动作x和y,我们写 hb(x,y)来表示x发生在y之前。

     
      
  • 如果x和y是同一个线程的动作,并且x在程序顺序中位于y之前,那么 hb(x,y)
  •   
  • 从对象的构造函数末尾到该对象的终结符(第12.6节)的开头有一个发生前的边缘。
  •   
  • 如果某个操作x与后续操作y同步,那么我们还有 hb(x,y)
  •   
  • 如果 hb(x,y) hb(y,z),则 hb(x,z)
  •   

此外,引入 synchronized-with relation 的唯一方法,即同步订单,可以执行以下操作:

  

同步动作引发与动作的同步关系,定义如下:

     
      
  • 监视器m上的解锁操作与m上的所有后续锁定操作同步(后续操作根据同步顺序定义)。
  •   
  • 对volatile变量(第8.3.1.4节)的写入v与任何线程的v的所有后续读取同步(其中后续根据同步顺序定义)。
  •   
  • 启动线程的操作与其启动的线程中的第一个操作同步。
  •   
  • 向每个变量写入默认值(零,false或null)与每个线程中的第一个操作同步。虽然在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时使用其默认初始化值创建的。
  •   
  • 线程T1中的最终操作与另一个检测到T1已终止的线程T2中的任何操作同步。 T2可以通过调用T1.isAlive()或T1.join()来完成此任务。
  •   
  • 如果线程T1中断线程T2,则T1的中断与任何其他线程(包括T2)确定T2已被中断的任何点同步(通过抛出InterruptedException或通过调用Thread.interrupted或Thread.isInterrupted) 。
  •   

它没有说“对象的构造发生在对对象上的方法的任何调用之前。然而,发生在之前的关系,指出有一个先发生过的边缘来自对象的构造函数的结尾,该对象的终结符(第12.6节)的开头。可能是关于的提示>从对象的构造函数的结尾到任意方法的开头的before-before edge!