gpt4 book ai didi

java - 没有 volatile 的双重检查锁定(但使用 VarHandle 释放/获取)

转载 作者:行者123 更新时间:2023-12-03 11:17:51 25 4
gpt4 key购买 nike

在某种程度上,这个问题相当简单。假设我有这个类:

static class Singleton {

}
我想为它提供一个单例工厂。我可以做(可能)显而易见的事情。我不会提及枚举可能性或任何其他,因为它们对我不感兴趣。
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 read以更高的代码复杂度为代价:
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
}
这几乎就是我们的代码近十年来一直在使用的东西。
问题是我可以用 release/acquire 让这更快吗? java-9 中添加的语义通过 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;
}

}
这会是一个有效且正确的实现吗?

最佳答案

是的,这是正确的,并且存在 on Wikipedia . (该字段是否易变并不重要,因为它只能从 VarHandle 访问。)
如果第一次读取看到一个过时的值,它将进入同步块(synchronized block)。由于同步块(synchronized block)涉及先发生关系,因此第二次读取将始终看到写入的值。即使在维基百科上,它也说顺序一致性丢失了,但它指的是字段;同步块(synchronized block)是顺序一致的,即使它们使用发布-获取语义。
所以第二次空检查永远不会成功,并且对象永远不会被实例化两次。
保证第二次读取将看到写入的值,因为它是在与计算值并存储在变量中时持有相同的锁的情况下执行的。
在 x86 上,所有负载都具有获取语义,因此唯一的开销是空检查。 Release-acquire 允许最终看到值(这就是为什么相关方法在 Java 9 之前被称为 lazySet,并且它的 Javadoc 使用了完全相同的词)。在这种情况下,同步块(synchronized block)可以防止这种情况发生。
指令可能不会被重新排序到同步块(synchronized block)中。

关于java - 没有 volatile 的双重检查锁定(但使用 VarHandle 释放/获取),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65171886/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com