java - 没有 volatile 的双重检查锁定

标签 java multithreading final java-memory-model double-checked-locking

我读过 this question关于如何进行双重检查锁定:

// 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 属性的情况下延迟加载字段(不是单例)。初始化后字段对象永远不会改变。

经过一些测试我的 final方法:

    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;
        }
    }
}

由于不需要 volatile,其好处可能是更快的访问时间,同时仍保持可重用 Publisher 类的简单性。


我使用 jcstress 对此进行了测试。 SafeDCLFinal 按预期工作,而 UnsafeDCLFinal 不一致(如预期)。在这一点上,我 99% 确定它有效,但请证明我错了。使用 mvn clean install -pl tests-custom -am 编译并使用 java -XX:-UseCompressedOops -jar tests-custom/target/jcstress.jar -t DCLFinal 运行。下面的测试代码(主要是修改过的单例测试类):

/*
 * SafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class SafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Safe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }


    @State
    public static class SafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), true);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * UnsafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class UnsafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Safe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }

    @State
    public static class UnsafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), false);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * Publisher.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class Publisher {

    public static <T> T publish(T val, boolean safe){
        if(safe){
            return new SafePublish<T>(val).get();
        }
        return new UnsafePublish<T>(val).get();
    }

    private static class UnsafePublish<T>{
        T val;

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

        public T get(){
            return val;
        }
    }

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

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

        public T get(){
            return val;
        }
    }
}

使用 java 8 测试,但至少应该可以使用 java 6+。 See docs


但我想知道这是否可行:

    // 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;
    }

或者:

    // 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;
    }

我相信这将基于 this oracle doc :

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

最佳答案

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

无论如何,相关方法在 "Safe Publication and Initialization in Java"如:

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;
    }
  }
}

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

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

此外,当您发现空 wrapper并使用其值时,您必须小心在锁下进行“回退”读取。在 return 语句中对 wrapper 进行第二次(第三次)读取也会破坏正确性,从而为您的合法比赛做好准备。

编辑:顺便说一句,如果您要发布的对象在内部被 final-s 覆盖,则可以切断 FinalWrapper 的中间人,并发布 instance 本身。

编辑 2:另见 LCK10-J. Use a correct form of the double-checked locking idiom ,并在评论中进行一些讨论。

关于java - 没有 volatile 的双重检查锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29883403/

相关文章:

java - Java 中的不可变类

java - AsyncResponse ConnectionCallback 不会在 Jersey 中触发

multithreading - Interlocked.CompareExchange 的返回值是否有充分的理由

c++ - 使用带有 4 个 for 循环的 openmp 进行并行化

constructor - 如何在构造函数中初始化最终类属性?

java - 在Table Layout中的TextViews的onClickListener中添加动态数据

java - java游戏中的子弹实现

java - 如何使用同一项目中另一个类的另一个 JPanel 更新 JPanel?

java - JTidyFilter/JTidyServlet 替代方案?

java - Java 中的 parallelStream 在哪些应用领域有用?