Java 双重检查锁定,无易失性

Java 双重检查锁定,无易失性,java,multithreading,final,java-memory-model,double-checked-locking,Java,Multithreading,Final,Java Memory Model,Double Checked Locking,我读过关于如何进行双重检查锁定的内容: // Double-check idiom for lazy initialization of instance fields private volatile FieldType field; FieldType getField() { FieldType result = field; if (result == null) { // First check (no locking) synchronized(thi

我读过关于如何进行双重检查锁定的内容:

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}
我的目标是在没有volatile属性的情况下懒洋洋地加载一个字段(不是单例)。初始化后,字段对象永远不会更改

在对我的最终方法进行一些测试后:

    private FieldType field;

    FieldType getField() {
        if (field == null) {
            synchronized(this) {
                if (field == null)
                    field = Publisher.publish(computeFieldValue());
            }
        }
        return fieldHolder.field;
    }



public class Publisher {

    public static <T> T publish(T val){
        return new Publish<T>(val).get();
    }

    private static class Publish<T>{
        private final T val;

        public Publish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }
}
使用Java8进行测试,但至少应使用Java6+


但我想知道这是否有效:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldHolder fieldHolder = null;
    private static class FieldHolder{
        public final FieldType field;
        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (fieldHolder == null) { // First check (no locking)
            synchronized(this) {
                if (fieldHolder == null) // Second check (with locking)
                    fieldHolder = new FieldHolder();
            }
        }
        return fieldHolder.field;
    }
甚至可能:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;
    private static class FieldHolder{
        public final FieldType field;

        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new FieldHolder().field;
            }
        }
        return field;
    }
或:

我相信这将在以下基础上起作用:

final字段的使用模型很简单:在对象的构造函数中为对象设置final字段;并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方编写对正在构造的对象的引用。如果紧跟其后,那么当另一个线程看到该对象时,该线程将始终看到该对象最终字段的正确构造版本。它还将看到最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样最新


不,这样不行

final
不能保证volatile所能保证的线程之间的可见性。您引用的Oracle文档表示,其他线程将始终看到对象最终字段的正确构造版本
final
保证在对象构造函数完成运行时,所有final字段都已构造和设置。因此,如果对象
Foo
包含最后一个字段
bar
bar
保证在
Foo
的构造函数完成时被构造

但是,
final
字段引用的对象仍然是可变的,对该对象的写入可能无法在不同线程中正确显示

因此,在您的示例中,不能保证其他线程看到已创建并可能创建另一个的
FieldHolder
对象,或者如果
FieldType
对象的状态发生任何修改,也不能保证其他线程会看到这些修改。
final
关键字只保证一旦其他线程看到了
FieldType
对象,就会调用其构造函数。

引用@Kicsi提到的内容,最后一部分是:

双重检查锁定不可变对象 如果Helper是不可变对象,则 辅助对象是最终的,然后双重检查锁定将在没有 要使用易失性字段。其思想是引用一个不可变的 对象(如字符串或整数)的行为应该大致相同 作为int或float的方式;读写对不可变对象的引用 对象是原子的

(重点是我的)

由于
FieldHolder
是不可变的,您确实不需要
volatile
关键字:其他线程将始终看到正确初始化的
FieldHolder
。据我所知,
字段类型
在通过
FieldHolder
从其他线程访问之前总是会被初始化

但是,如果
FieldType
不是不可变的,则仍然需要进行适当的同步。因此,我不确定您是否会从避免使用
volatile
关键字中获益匪浅


如果它是不可变的,那么按照上面的引文,您根本不需要
字段持有者。

第一件事:您试图做的事情充其量是危险的。当人们试图在期末考试中作弊时,我有点紧张。Java语言为您提供了
volatile
作为处理线程间一致性的工具。使用它

无论如何,相关方法如中所述 作为:

按照外行的说法,它是这样工作的<当我们观察到
wrapper
为null时,code>synchronized
会产生正确的同步——换句话说,如果我们完全放弃第一个检查并将
synchronized
扩展到整个方法体,那么代码显然是正确的<在
FinalWrapper
中,code>final保证如果我们看到非空的
wrapper
,它是完全构造的,并且所有
单例
字段都是可见的——这将从
wrapper
的快速读取中恢复

请注意,它在字段中传递的是
FinalWrapper
,而不是值本身。如果
instance
在没有
FinalWrapper
的情况下发布,所有赌注都将被取消(用外行的话说,这是过早发布)。这就是为什么您的
Publisher.publish
不起作用的原因:只是将值放在final字段中,读回,然后不安全地发布它是不安全的——这与只将裸
实例写出来非常相似

此外,当您发现空的
包装器
,并使用其值时,必须小心地在锁下读取“回退”。在return语句中第二次(第三次)读取
wrapper
,也会破坏正确性,使您处于合法的竞争状态

编辑:顺便说一句,如果您发布的对象内部覆盖了
final
-s,那么您可以剪切
FinalWrapper
的中间人,并发布
实例本身

编辑2:另请参见,以及评论中的一些讨论。

简而言之 没有volatile或wrapper类的代码版本取决于JVM运行的底层操作系统的内存模型

带有包装类的版本是一种已知的替代方案,称为设计模式,它依赖于
类加载器
契约,即任何给定类在第一次访问时最多加载一次,并且以线程安全的方式加载

需要
volatile
开发人员对代码执行的看法主要是
    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;
    private static class FieldHolder{
        public final FieldType field;

        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new FieldHolder().field;
            }
        }
        return field;
    }
    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new Object(){
                        public final FieldType field = computeFieldValue();
                    }.field;
            }
        }
        return field;
    }
public class FinalWrapperFactory {
  private FinalWrapper wrapper;

  public Singleton get() {
    FinalWrapper w = wrapper;
    if (w == null) { // check 1
      synchronized(this) {
        w = wrapper;
        if (w == null) { // check2
          w = new FinalWrapper(new Singleton());
          wrapper = w;
        }
      }
    }
    return w.instance;
  }

  private static class FinalWrapper {
    public final Singleton instance;
    public FinalWrapper(Singleton instance) {
      this.instance = instance;
    }
  }
}
public enum EnumSingleton {
    /**
     * using enum indeed avoid reflection intruding but also limit the ability of the instance;
     */
    INSTANCE;

    SingletonTypeEnum getType() {
        return SingletonTypeEnum.ENUM;
    }
}

/**
 * Singleton:
 * The JLS guarantees that a class is only loaded when it's used for the first time
 * (making the singleton initialization lazy)
 *
 * Thread-safe:
 * class loading is thread-safe (making the getInstance() method thread-safe as well)
 *
 */
private static class SingletonHelper {
    private static final LazyInitializedSingleton INSTANCE = new LazyInitializedSingleton();
}
  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                synchronized(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }