我知道AtomicReference
有compareAndSet
,但我觉得我想做的就是这个
private final AtomicReference<Boolean> initialized = new AtomicReference<>( false );
...
atomicRef.compareSetAndDo( false, true, () -> {
// stuff that only happens if false
});
这可能也会奏效,可能会更好。
atomicRef.compareAndSet( false, () -> {
// stuff that only happens if false
// if I die still false.
return true;
});
我注意到有一些新的功能结构,但我不确定它们中是否有任何我正在寻找的东西。
任何新构造都可以这样做吗?如果是这样,请提供一个例子。
更新
为了试图简化我的问题,我试图找到一种不那么容易出错的方法来保护代码在&#34;为对象做一次&#34;或者(真的)懒惰的初始化时尚,我知道我的团队中的一些开发人员发现compareAndSet
令人困惑。
答案 0 :(得分:4)
保护代码在&#34;为对象执行一次&#34;
如何实现这一点取决于您希望其他线程在此期间尝试执行相同的操作。如果你让他们跑过CAS,他们可能会观察处于中间状态的事情,而成功的一个线程就会采取行动。
或(真的)懒惰的初始化时尚
如果你将它用于延迟初始化器,那么该构造不是线程安全的,因为&#34;被初始化&#34; boolean可以由一个线程设置为true,然后执行该块,而另一个线程观察到true-state但是读取空结果。
如果可以接受多个并发/重复初始化尝试,并且一个对象最终获胜而其他对象被GC丢弃,则可以使用Atomicreference::updateAndGet。更新方法应该是无副作用的。
否则,您应该使用带有变量引用字段的双重检查锁定模式。
当然,您始终可以将其中的任何一个打包为更高阶函数,该函数返回Runnable
或Supplier
,然后将其分配给最终字段。
// == FunctionalUtils.java
/** @param mayRunMultipleTimes must be side-effect-free */
public static <T> Supplier<T> instantiateOne(Supplier<T> mayRunMultipleTimes) {
AtomicReference<T> ref = new AtomicReference<>(null);
return () -> {
T val = ref.get(); // fast-path if already initialized
if(val != null)
return val;
return ref.updateAndGet(v -> v == null ? mayRunMultipleTimes.get() : v)
};
}
// == ClassWithLazyField.java
private final Supplier<Foo> lazyInstanceVal = FunctionalUtils.instantiateOne(() -> new Foo());
public Foo getFoo() {
lazyInstanceVal.get();
}
您可以通过这种方式轻松封装各种自定义控制流和锁定模式。 Here are two of my own.
答案 1 :(得分:3)
compareAndSet
返回true;如果实际值不等于预期值,则返回false。
所以只需使用
if (ref.compareAndSet(expectedValue, newValue)) {
...
}
那就是说,我并不真正理解你的例子,因为你将true和false传递给一个以对象引用为参数的方法。而你的第二个例子与第一个例子不同。如果第二个是你想要的,我认为你所追求的是
ref.getAndUpdate(value -> {
if (value.equals(expectedValue)) {
return someNewValue(value);
}
else {
return value;
}
});
答案 2 :(得分:2)
你的事情过于复杂。只是因为现在有lambda表达式,你不需要用lambdas解决所有问题:
private volatile boolean initialized;
…
if(!initialized) synchronized(this) {
if(!initialized) {
// stuff to be done exactly once
initialized=true;
}
}
双重检查锁定可能没有良好的声誉,但对于非static
属性,几乎没有其他选择。
如果您考虑多个线程在未初始化状态下同时访问它并希望保证该操作只运行一次,并且它已完成,则在执行相关代码之前,{{1} }对象不会帮助你。
只有一个线程可以成功执行Atomic…
,但由于失败意味着该标志已经具有新值,即已初始化,所有其他线程将继续进行,就好像“准备完成的事情“已经完成,而它可能仍在运行。另一种方法是首先读取标志并有条件地执行这些东西,然后compareAndSet(false,true)
,但这允许多个并发执行“stuff”。这也是compareAndSet
或updateAndGet
所提供的功能。
为了保证在继续之前完全执行一次,如果当前执行“stuff”,则必须阻止线程。上面的代码就是这样做的。请注意,一旦完成“填充”,就不再有锁定,accumulateAndGet
读取的性能特征与volatile
读取的性能特征相同。
编程更简单的唯一解决方案是使用Atomic…
:
ConcurrentMap
它可能看起来有点过大,但它提供了所需的性能特征。它将使用private final ConcurrentHashMap<String,Boolean> initialized=new ConcurrentHashMap<>();
…
initialized.computeIfAbsent("dummy", ignore -> {
// stuff to do exactly once
return true;
});
(或者,依赖于实现的排除机制)保护初始计算,但在后续查询中使用synchronized
语义执行单个读取。
如果你想要一个更轻量级的解决方案,你可以继续使用本答案开头所示的双重检查锁定...