双重检查锁定,无易失性(但具有VarHandle释放/获取功能) [英] double check locking without volatile (but with VarHandle release/acquire)

查看:49
本文介绍了双重检查锁定,无易失性(但具有VarHandle释放/获取功能)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从某种意义上说,这个问题很容易.假设我有这个课程:

The question is rather easy, in a way. Suppose I have this class:

static class Singleton {

}

我想为此提供一个单例工厂.我可以做(可能)很明显.我将不提及枚举的可能性或其他任何可能性,因为它们对我而言没有任何意义.

And I want to provide a singleton factory for it. I can do the (probably) obvious. I am not going to mention the enum possibility or any other, as they are of no interest to me.

static final class SingletonFactory {

    private static volatile Singleton singleton;

    public static Singleton getSingleton() {
        if (singleton == null) { // volatile read
            synchronized (SingletonFactory.class) {
                if (singleton == null) { // volatile read
                    singleton = new Singleton(); // volatile write
                }
            }
        }
        return singleton; // volatile read
    }
}

我可以以更高的代码复杂性为代价来摆脱一个 volatile读:

I can get away from one volatile read with the price of higher code complexity:

public static Singleton improvedGetSingleton() {
    Singleton local = singleton; // volatile read
    if (local == null) {
        synchronized (SingletonFactory.class) {
           local = singleton; // volatile read
           if (local == null) {
               local = new Singleton();
               singleton = local; // volatile write
           }
        }
    }

    return local; // NON volatile read
}

这差不多是我们的代码已经使用了近十年了.

This is pretty much what our code has been using for close to a decade now.

问题是,我是否可以通过 VarHandle java-9 中添加的 release/acquire 语义使其更快?

The question is can I make this even faster with release/acquire semantics added in java-9 via VarHandle:

static final class SingletonFactory {

    private static final SingletonFactory FACTORY = new SingletonFactory();

    private Singleton singleton;

    private static final VarHandle VAR_HANDLE;

    static {
        try {
            VAR_HANDLE = MethodHandles.lookup().findVarHandle(SingletonFactory.class, "singleton", Singleton.class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Singleton getInnerSingleton() {

        Singleton localSingleton = (Singleton) VAR_HANDLE.getAcquire(FACTORY); // acquire

        if (localSingleton == null) {
            synchronized (SingletonFactory.class) {
                localSingleton = (Singleton) VAR_HANDLE.getAcquire(FACTORY); // acquire
                if (localSingleton == null) {
                    localSingleton = new Singleton();
                    VAR_HANDLE.setRelease(FACTORY, localSingleton); // release
                }
            }
        }

        return localSingleton;
    }
    
}

这将是有效且正确的实现吗?

Would this be a valid and correct implementation?

推荐答案

是的,这是正确的,并且存在在Wikipedia上.(因为该字段只能从 VarHandle 进行访问,所以该字段是易变的).

Yes, this is correct, and it is present on Wikipedia. (It doesn't matter that the field is volatile, since it is only ever accessed from VarHandle.)

如果第一次读取看到过时的值,它将进入同步块.由于同步块涉及事前发生的关系,因此第二次读取将始终看到写入的值.即使在Wikipedia上,它也表示丢失了顺序一致性,但它指的是字段.同步块顺序一致,即使它们使用发布获取语义.

If the first read sees a stale value, it enters the synchronized block. Since synchronized blocks involve happen-before relationships, the second read will always see the written value. Even on Wikipedia it says sequential consistency is lost, but it refers to the fields; synchronized blocks are sequentially consistent, even though they use release-acquire semantics.

因此第二个null检查将永远不会成功,并且该对象也永远不会实例化两次.

So the second null check will never succeed, and the object is never instantiated twice.

保证第二次读取将看到写入的值,因为它的执行与计算值并将其存储在变量中时的锁相同.

It is guaranteed that the second read will see the written value, because it is executed with the same lock held as when the value was computed and stored in the variable.

在x86上,所有负载都具有语义,因此唯一的开销就是空检查.Release-acquire允许最终看到的值(这就是为什么相关方法在Java 9之前被称为 lazySet 的原因,而其Javadoc使用了完全相同的单词).在这种情况下,可以通过同步块来防止这种情况.

On x86 all loads have acquire semantics, so the only overhead would be the null check. Release-acquire allows values to be seen eventually (that's why the relevant method was called lazySet before Java 9, and its Javadoc used that exact same word). This is prevented in this scenario by the synchronized block.

指令可能不会重新排序并放入同步块中.

Instructions may not be reordered out and into synchronized blocks.

这篇关于双重检查锁定,无易失性(但具有VarHandle释放/获取功能)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆