Lazy loading reference implementation
Being impressed by Guava's computing map feature, I'm searching for a sort of "computing reference开发者_StackOverflow" - a lazy loading reference implementation that parallel's Guava's ease of use, by which I mean it handles all locking, loading, and exception handling under the hood, only exposing a get()
method.
After a brief search turned up nothing, I quickly rolled my own as a proof of concept:
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;
}
}
This ComputingRef
could be anonymously extended to implement call()
, which functions as the factory method:
ComputingRef<MyObject> lazySingletonRef = new ComputingRef<MyObject>() {
@Override
public MyObject call() {
//fetch MyObject from database and return
}
};
I'm not satisfied that this implementation is optimal, but it demonstrates what I'm after.
I later found this example from the T2 Framework, which appears to be more complex.
Now my questions are:
- How can my above code be improved?
- How does it compare to the T2 example, and what advantages are offered by that example's greater complexity?
- Are there other implementations of a lazy loading reference that I've missed in my search?
EDIT: Updated my implementation to use a local variable as suggested by @irreputable's answer - please upvote it if you find the above example useful.
See Suppliers.memoize(Supplier)
to lazily initialize a value.
It's the good old double-checked locking idiom. You should add a local variable for performance. In your impl, you have 2 volatile reads in the fast path (when referent is set). Check http://en.wikipedia.org/wiki/Double-checked_locking
Anyway, here's how I would do it (and then I'd worry about performance later):
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();
}
}
The only 'snag' with this approach is that there is a race condition that could result multiple instantiations of the referent object (esp. if the ComputingRef
is shared across a large number of threads that all hit get()
at the same time). If instantiating the referent class is so expensive or you want to avoid multiple-instantiation at all costs, then I'd go with your double-checked locking as well.
You also have to make sure that the referent object cleans up after itself. Otherwise, if the compareAndSet()
fails, then make sure to perform any necessary cleanup.
(Note that if the referent needs to be a singleton, then I'd use the initialization on demand holder idiom instead.)
精彩评论