Guava has a feature request which is asking to provide the ability to forget memoized value on a given supplier.
On top on that I needed another functionality where if during the calculation of expensive value another thread would invalidate the data, the memoisation should retry (guava LoadableCache
doesn't provide this feature: invalidation during calculation is ignored).
I couldn't wait for this feature so I wrote this myself. First, an interface for forgettable supplier:
public interface ForgettableSupplier<T> extends Supplier<T> {
public void forget();
}
Then, a base class, mostly taken from here:
public abstract class DoubleCheck<T> extends ReentrantReadWriteLock {
protected abstract T create();
protected abstract T retrieve();
protected abstract boolean invalid(T value);
public T get() {
try {
readLock().lock();
T value = retrieve();
while (invalid(value)) { // this spins until no more invalidations are made
readLock().unlock();
writeLock().lock();
value = retrieve();
try {
if (invalid(value)) {
value = create();
}
} finally {
readLock().lock();
writeLock().unlock();
}
}
return value;
} finally {
readLock().unlock();
}
}
}
And finally, the static utility that combines everything together:
public class Suppliers {
private Suppliers() {
}
public static <T> ForgettableSupplier<T> memoize(Supplier<T> delegate) {
return new ForgettableMemoizingSupplier<>(delegate);
}
private static final class ForgettableMemoizingSupplier<T> implements ForgettableSupplier<T> {
private final Supplier<T> delegate;
private volatile T t;
private final AtomicBoolean needsToBeForgotten = new AtomicBoolean(false);
private final DoubleCheck<T> doubleCheck = new DoubleCheck<T>() {
@Override
protected T create() {
needsToBeForgotten.set(false);
return ForgettableMemoizingSupplier.this.t = delegate.get();
}
@Override
protected T retrieve() {
return ForgettableMemoizingSupplier.this.t;
}
@Override
protected boolean invalid(final T t) {
return (ForgettableMemoizingSupplier.this.t == null) || needsToBeForgotten.get();
}
};
private ForgettableMemoizingSupplier(final Supplier<T> delegate) {
this.delegate = delegate;
}
@Override
public void forget() {
needsToBeForgotten.set(true);
}
@Override
public T get() {
return doubleCheck.get();
}
}
}