Java 双重检查锁定保证对象的状态?(实践中的并发)

Java 双重检查锁定保证对象的状态?(实践中的并发),java,concurrency,double-checked-locking,Java,Concurrency,Double Checked Locking,我在实践中阅读并发,有一些误解。 引述: DCL的真正问题是假设 在读取共享对象引用时,如果没有 同步是指错误地看到过时的值(在本例中, 无效);在cse中,DCL习惯用法通过尝试来补偿这种风险 又一次锁上了。但最坏的情况实际上相当严重 错误-可以看到参考的当前值,但 对象状态的过时值,意味着对象可以 被视为处于无效或不正确的状态 Brian Goetz写到DCL将在当前内存模型中使用volatile: public class DoubleCheckLociing{ private s

我在实践中阅读并发,有一些误解。
引述:

DCL的真正问题是假设 在读取共享对象引用时,如果没有 同步是指错误地看到过时的值(在本例中, 无效);在cse中,DCL习惯用法通过尝试来补偿这种风险 又一次锁上了。但最坏的情况实际上相当严重 错误-可以看到参考的当前值,但 对象状态的过时值,意味着对象可以 被视为处于无效或不正确的状态

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;
   }
}
我不确定是否理解了关于国家的短语是正确的

让我们设想
资源
类如下所示:

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
始终返回
correct
资源,该资源始终返回correct
k
(10)和
date

如果
volatile
处于适当位置,您确实有这些保证。如果没有
volatile
,您就不会

当一个线程写入易失性变量
resource
时,该操作包括一个“内存屏障”,确保在此之前写入的所有内容(如实例字段的初始化)都首先写入系统内存

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


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

使用
volatile
,您就有了这些保证。如果没有
volatile
,您就不会

当一个线程写入易失性变量
resource
时,该操作包括一个“内存屏障”,确保在此之前写入的所有内容(如实例字段的初始化)都首先写入系统内存

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

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

我是否保证getInstance总是返回正确的资源,而这些资源总是返回正确的k(10)和日期

是和否。正如@Matt指出的,如果该字段是
易失的
,那么您可以保证
资源
的所有字段在被另一个线程访问时都会被适当地发布,这是您引用的双重检查锁定示例的重要部分。
易失性
内存屏障确保了这一点

但是,您的
资源
包含可能是可变的非最终字段(如果添加或未列出setter方法)。如果将字段设置为
final
,那么实际上可以省去
volatile
,因为
final
字段保证在发布对
资源的引用时发布

private final Date date = new Date();
private final int k = 10;
然而,正如您所提到的,这并不能完全节省您的时间,因为
Date
也是可变的。您需要确保您的代码(或其他代码)没有在
date
字段上调用任何setter方法,然后您就可以了

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

我是否保证getInstance总是返回正确的资源,而这些资源总是返回正确的k(10)和日期

是和否。正如@Matt指出的,如果该字段是
易失的
,那么您可以保证
资源
的所有字段在被另一个线程访问时都会被适当地发布,这是您引用的双重检查锁定示例的重要部分。
易失性
内存屏障确保了这一点

但是,您的
资源
包含可能是可变的非最终字段(如果添加或未列出setter方法)。如果将字段设置为
final
,那么实际上可以省去
volatile
,因为
final
字段保证在发布对
资源的引用时发布

private final Date date = new Date();
private final int k = 10;
然而,正如您所提到的,这并不能完全节省您的时间,因为
Date
也是可变的。您需要确保您的代码(或其他代码)没有在
date
字段上调用任何setter方法,然后您就可以了


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

我是否保证getInstance总是返回正确的资源,而这些资源总是返回正确的k(10)和日期?。所以你只想确认Brian Goetz是否正确?我需要确认我的理解是正确的。我以前的理解完全是另一回事。我认为这是两次阅读共享参考的问题。第一个可以回来=nul,但第二个可以是空的,你在网站上找不到一个答案来证实这一点?我的术语能力不强,因此我无法正确构建搜索字符串标签与这个问题有关吗?我是否保证getInstance总是返回正确的资源,而这些资源总是返回正确的k(10)和日期?。所以你只想确认Brian Goetz是否正确?我需要确认我的理解是正确的。我以前的理解完全是另一回事。我认为这是两次阅读共享参考的问题。第一个可以回来=nul,但第二个可以为空