Search code examples
guavagoogle-guava-cache

Can weakValues() and expireAfterAccess() be combined?


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?


Solution

  • 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.