Double-Check-Locking 保证对象的状态 ? (实践中的并发)

Double-Check-Locking guarantees state of the object ? (concurrency in practice)

我在看并发实践,有些误解。
引用:

the real problem with DCL is the assumption that the worst thing that can happen when reading the shared object reference without synchronization is to erroneously see a stale value (in this case, null); in that cse the DCL idiom compensates for this risk by trying again with the lock held. But the worst case is actually considerably wrong - it is possible to see a current value of the reference but stale values for the object's states, meaning that the object could be seen to be in an invalid or incorrect state.

在 Brian Goetz 写道 DCL 将在使用 volatile 的当前内存模型中工作之后:

public class DoubleCheckLociing{

   private static volatile Resource resource;

   public static Resource getInstance(){
       if(resource == null){
           synchronized(DoubleCheckLociing.class){
               if(resource == null){
                   resource = new Resource();
               }
            }
       } 
       return resource;
   }
}

我不确定关于状态的短语是否理解正确。

让我们想象一下 Resource class 看起来像这样:

class Resource{
    private Date date = new Date();//mutable thread unsafe class
    private int k = 10;

    public Date getDate(){
        return date;
    }

   public int getK(){
        return k;
    }

}

我是否可以保证 getInstance 始终 return correct 始终 return 正确 k (10) 和 date 的资源?

有了 volatile,您就拥有了这些保证。没有 volatile 你就不会。

当一个线程写入 volatile 变量 resource 时,该操作包括一个 'memory barrier' 以确保它之前写入的所有内容(例如实例字段的初始化)都被写入系统内存第一.

当另一个线程读取 resource 时,它包含一个内存屏障,确保之后执行的任何读取都将看到在读取之前从系统内存中缓存的值。

这两个内存屏障确保如果一个线程看到一个初始化的 resource 变量,那么它也会看到该对象中正确初始化的字段。

Does I have guaranties that getInstance always return correct resource which always return correct k (10) and date?

是也不是。正如@Matt 指出的那样,如果该字段是 volatile 那么您可以保证 Resource 的所有字段在被另一个线程访问时被适当地发布,这是 [= 的重要部分30=] 你引用的例子。 volatile memory-barriers 确保这一点。

但是,您的 Resource 包含可能可变的 non-final 字段(如果添加或未列出 setter 方法)。如果你让你的字段 final 那么实际上你可以省去 volatile 因为 final 字段保证在发布对你的 Resource 的引用时发布。

private final Date date = new Date();
private final int k = 10;

正如您提到的那样,这并不能完全拯救您,因为 Date 也是可变的。您需要确保您的代码(或其他代码)没有在 date 字段上调用任何 setter 方法,然后您就可以了。

这是一个很好的例子,说明了在线程程序中跟踪可变性的重要性。