Guava的computing map feature给我留下了深刻的印象,我正在寻找一种“计算参考” - 一种延迟加载参考实现,它兼顾了Guava的易用性,我的意思是它处理所有锁定,加载和引擎盖下的异常处理,只暴露get()
方法。
经过简短的搜索后,我很快就将自己作为概念证明:
public abstract class ComputingRef<T> implements Callable<T> {
private volatile T referent = null;
private Lock lock = new ReentrantLock();
public T get() {
T temp = referent;
if (temp == null) {
lock.lock();
try {
temp = referent;
if (temp == null) {
try {
referent = temp = call();
}
catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
else {
throw new RuntimeException(e);
}
}
}
}
finally {
lock.unlock();
}
}
return temp;
}
}
可以匿名扩展此ComputingRef
以实现call()
,其作为工厂方法:
ComputingRef<MyObject> lazySingletonRef = new ComputingRef<MyObject>() {
@Override
public MyObject call() {
//fetch MyObject from database and return
}
};
我不满意这种实现是最佳的,但它证明了我所追求的目标。
我后来发现this example from the T2 Framework,似乎更复杂。
现在我的问题是:
编辑:根据@irreputable's answer的建议更新了我的实施以使用本地变量 - 如果您发现上述示例有用,请点击它。
答案 0 :(得分:23)
请Suppliers.memoize(Supplier)
懒惰地初始化一个值。
答案 1 :(得分:2)
无论如何,我会这样做(然后我会担心以后的表现):
public abstract class ComputingRef<T> implements Callable<T> {
private final AtomicReference<T> ref = new AtomicReference<T>();
public T get() {
if (ref.get() == null) {
try {
final T newValue = call();
if (ref.compareAndSet(null, newValue)) {
return newValue;
}
} catch (final Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
}
}
return ref.get();
}
}
这种方法唯一的“障碍”是存在一种竞争条件,可能导致对象对象的多个实例化(特别是如果ComputingRef
在大量线程中共享get()
全部命中{{ 1}}同时)。如果实例化referent类是如此昂贵,或者你想不惜一切代价避免多次实例化,那么我也会使用双重检查锁定。
您还必须确保引用对象自行清理。否则,如果compareAndSet()
失败,请确保执行任何必要的清理。
(请注意,如果指示对象必须是单身,那么我会使用initialization on demand holder idiom代替。)
答案 2 :(得分:1)
这是旧的双重检查锁定习语。您应该为性能添加局部变量。在你的impl中,你在快速路径中有2个易失性读取(当设置了指示对象时)。查看http://en.wikipedia.org/wiki/Double-checked_locking