gpt4 book ai didi

java - 没有 volatile 的惰性初始化/内存

转载 作者:搜寻专家 更新时间:2023-10-31 19:42:56 25 4
gpt4 key购买 nike

Java 内存模型似乎没有定义本地缓存的“刷新”和“刷新”,人们只是为了简单起见才这样调用它,但实际上“先发生”关系意味着以某种方式刷新和刷新(会如果你能解释,但不是问题的直接部分,那就太好了。

结合关于 Java Memory Model in the JLS 的部分,这让我感到非常困惑。没有以易于理解的方式编写。

那么请问我在下面的代码中所做的假设是否正确,是否能保证正确运行?

它部分基于 Double-checked locking 上维基百科文章中提供的代码,但是作者在那里使用了一个包装器类 (FinalWrapper),但我并不完全清楚其中的原因。也许支持 null 值?

public class Memoized<T> {
private T value;
private volatile boolean _volatile;
private final Supplier<T> supplier;

public Memoized(Supplier<T> supplier) {
this.supplier = supplier;
}

public T get() {
/* Apparently have to use local variable here, otherwise return might use older value
* see https://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
*/
T tempValue = value;

if (tempValue == null) {
// Refresh
if (_volatile);
tempValue = value;

if (tempValue == null) {
// Entering refreshes, or have to use `if (_volatile)` again?
synchronized (this) {
tempValue = value;

if (tempValue == null) {
value = tempValue = supplier.get();
}

/*
* Exit should flush changes
* "Flushing" does not actually exists, maybe have to use
* `_volatile = true` instead to establish happens-before?
*/
}
}
}

return tempValue;
}
}

我还读到构造函数调用可以内联和重新排序,从而导致对未初始化对象的引用(参见 this comment on a blog)。那么直接分配供应商的结果是否安全,还是必须分两步完成?

value = tempValue = supplier.get();

两步:

tempValue = supplier.get();
// Reorder barrier, maybe not needed?
if (_volatile);
value = tempValue;

编辑:这个问题的标题有点误导,目的是减少 volatile 字段的使用。如果初始化值已经在线程的缓存中,则直接访问value,而不需要再次在主内存中查找。

最佳答案

如果你只有几个单例,你可以减少 volatile 的使用。注意:您必须为每个单例重复此代码。

enum LazyX {
;
static volatile Supplier<X> xSupplier; // set somewhere before use

static class Holder {
static final X x = xSupplier.get();
}

public static X get() {
return Holder.x;
}
}

如果你认识 Supplier,这会变得更简单

enum LazyXpensive {
;

// called only once in a thread safe manner
static final Xpensive x = new Xpensive();

// after class initialisation, this is a non volatile read
public static Xpensive get() {
return x;
}
}

您可以通过使用 Unsafe

来避免使字段变得不稳定
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.function.Supplier;

public class LazyHolder<T> {
static final Unsafe unsafe = getUnsafe();
static final long valueOffset = getValueOffset();

Supplier<T> supplier;
T value;

public T get() {
T value = this.value;
if (value != null) return value;

return getOrCreate();
}

private T getOrCreate() {
T value;
value = (T) unsafe.getObjectVolatile(this, valueOffset);
if (value != null) return value;

synchronized (this) {
value = this.value;
if (value != null) return value;
this.value = supplier.get();
supplier = null;
return this.value;
}
}


public static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError(e);
}
}

private static long getValueOffset() {
try {
return unsafe.objectFieldOffset(LazyHolder.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
}

但是,进行额外的查找是一种微观优化。如果您愿意为每个线程执行一次同步命中,则完全可以避免使用 volatile。

关于java - 没有 volatile 的惰性初始化/内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54099881/

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