首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >双重检查锁定没有易失性

双重检查锁定没有易失性
EN

Stack Overflow用户
提问于 2015-04-26 20:54:20
回答 4查看 9.3K关注 0票数 29

我读过这个问题关于如何进行双重检查的锁定:

代码语言:javascript
复制
// 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;
}

我的目标是在没有易失性属性的情况下延迟加载字段(而不是单例)工作。字段对象在初始化后从未更改。

在测试了我的最后一种方法之后:

代码语言:javascript
复制
    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;
        }
    }
}

这样做的好处可能是由于不需要易失性而缩短了访问时间,同时仍然保持了可重用Publisher类的简单性。

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

代码语言:javascript
复制
/*
 * 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+一起工作。见文档

但我不知道这是否可行:

代码语言:javascript
复制
    // 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;
    }

甚至:

代码语言:javascript
复制
    // 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;
    }

或者:

代码语言:javascript
复制
    // 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;
    }

我相信这将在这个甲骨文医生基础上起作用:

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-05-05 09:09:38

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

无论如何,相关的方法在“Java中的安全发布和初始化”中描述为:

代码语言:javascript
复制
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;
    }
  }
}

这是外行的条件,是这样的。当我们将synchronized视为null时,wrapper会产生适当的同步--换句话说,如果我们完全放弃第一个检查并将synchronized扩展到整个方法主体,那么代码显然是正确的。final in FinalWrapper保证当我们看到了非空的wrapper,它是完全构造的,所有的Singleton字段都是可见的--这是从wrapper的读取中恢复过来的。

注意,它在字段中传递的是FinalWrapper,而不是值本身。如果instance是在没有FinalWrapper的情况下发布的,那么所有的赌注都会被取消(从外行人的角度来说,这是不成熟的)。这就是为什么您的Publisher.publish不能正常工作的原因:只通过最后一个字段将值放回、重新读取并不安全地发布它都是不安全的--这非常类似于只将裸露的instance写入。

此外,当您发现空wrapper并使用它的值时,您必须小心地在锁下读取“回退”。第二次(第三次)阅读“回报”中的wrapper也会破坏正确性,使您进入一个合法的种族。

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

编辑2:请参见,LCK10-J.使用正确的复核锁定成语形式。,以及注释中的一些讨论。

票数 30
EN

Stack Overflow用户

发布于 2015-04-30 15:25:53

引用@Kicsi提到的“双重检查锁坏了”声明,最后一节是:

双重检查锁定不可变对象 如果Helper是一个不可变的对象,因此Helper的所有字段都是最终的,那么双重检查的锁定就可以工作,而不必使用易失性字段。其思想是,对不可变对象(例如字符串或Integer)的引用应该以与int或float相似的方式运行;读取和写入对不可变对象的引用是原子的。

(重点是我的)

由于FieldHolder是不可变的,所以您实际上不需要volatile关键字:其他线程总是会看到一个正确初始化的FieldHolder。据我所知,在通过FieldType从其他线程访问它之前,总是会对它进行初始化。

但是,如果FieldType不是不可变的,则仍然需要适当的同步。因此,我不确定避免使用volatile关键字会有多大好处。

但是,如果它是不可变的,那么您就根本不需要遵循上述引号的FieldHolder

票数 2
EN

Stack Overflow用户

发布于 2019-03-28 03:50:29

使用Enum或嵌套的静态类助手来进行延迟初始化,否则,如果初始化不会花费太多(空间或时间),只需使用静态初始化即可。

代码语言:javascript
复制
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();
}

“双重检查锁坏了”声明

通过此更改,可以通过声明助手字段为易失性来使双重检查锁定成语工作。这在JDK4和更早的时候是行不通的。

代码语言:javascript
复制
  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                synchronized(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29883403

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档