延迟加载参考实现

时间:2011-09-23 04:57:31

标签: java reference lazy-loading guava

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,似乎更复杂。

现在我的问题是:

  • 我的上述代码如何改进?
  • 与T2示例相比如何?该示例的复杂性有哪些优势?
  • 我的搜索中是否有其他延迟加载引用的实现?

编辑:根据@irreputable's answer的建议更新了我的实施以使用本地变量 - 如果您发现上述示例有用,请点击它。

3 个答案:

答案 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