I want to do something like this:
CacheBuilder
.newBuilder()
.maximumSize(CONFIG.cacheMaxSize())
.expireAfterAccess(CONFIG.cacheTimeout(),
CONFIG.cacheTimeUnit())
.weakValues()
.build(cacheLoader);
The behavior I expect is that an entry will only be expired if the value is not referenced AND the expiration time has passed. Is that how this usage will work?
Not directly, since the weak value can be garbage collected as soon as there are no more strong references to the object. What you could do however is use a ForwardingCache
backed by two separate caches, a weak-value cache and a timed-expiry cache, so that the time-based cache holds a strong reference to the object thereby keeping it in the weak-value cache. It'd look something like this:
public class WeakValuedExpiringCache<K, V> extends ForwardingCache<K, V> {
private final Cache<K, V> expiringCache;
private final Cache<K, V> weakCache;
public WeakValuedExpiringCache(CacheBuilder expiringSpec) {
expiringCache = expiringSpec.build();
weakCache = CacheBuilder.newBuilder().weakValues().build();
}
// weakCache is the canonical cache since it will hold values longer than
// expiration if there remain other strong references
protected Cache<K, V> delagate() {
return weakCache;
}
@override
public V get(K key, Callable<? extends V> valueLoader)
throws ExecutionException {
// repopulate the expiring cache if needed, and update the weak cache
V value = expiringCache.get(key, valueLoader);
weakCache.put(key, value); // don't call super.put() here
}
@Override
public void put(K key, V value) {
expiringCache.put(key, value);
super.put(key, value);
}
// Handle putAll(), cleanUp(), invalidate(), and invalidateAll() similarly
}
You can do the same thing with a ForwardingLoadingCache
as well, just like .get()
above you should load the value from the expiringCache
and .put()
it into the weakCache
in the relevant loading methods.