I want to create a memoized version of a given Supplier
such that multiple threads can use it concurrently with the guarantee that the original supplier's get()
is called at most once, and that all of the threads see that same result. Double-checked locking seems like a good fit.
class CachingSupplier<T> implements Supplier<T> {
private T result = null;
private boolean initialized = false;
private volatile Supplier<? extends T> delegate;
CachingSupplier(Supplier<? extends T> delegate) {
this.delegate = Objects.requireNonNull(delegate);
}
@Override
public T get() {
if (!this.initialized && this.delegate != null) {
synchronized (this) {
Supplier<? extends T> supplier = this.delegate;
if (supplier != null) {
this.result = supplier.get();
this.initialized = true;
this.delegate = null;
}
}
}
return this.result;
}
}
My understanding is that in this case delegate
needs to be volatile
because otherwise the code in the synchronized
block might be reordered: the write to delegate
could happen before the write to result
, possibly exposing result
to other threads before it has been fully initialized. Is that correct?
So, normally this would entail a volatile read of delegate
outside of the synchronized
block on each invocation, only entering the synchronized
block at most once per contending thread while result
is uninitialized and then never again.
But once result
has been initialized, is it possible to also avoid the cost, however negligible, of the unsynchronized volatile read of delegate
on subsequent invocations by first checking the non-volatile flag initialized
and short-circuiting? Or does this buy me absolutely nothing over normal double-checked locking? Or does it somehow hurt performance more than it helps? Or is it actually broken?