1

I basically need a map where entries would expire after a specific known time period and then are removed. The way it's being used in my app is not really a cache but it seems Guava cache can serve the purpose. Would this be the right choice? One thing is I'm going to need to query if the map is empty and I saw that Guava has only a size function which its documentation says is only an approximation.

Hanna Khalil
  • 975
  • 1
  • 10
  • 28
  • Why do you need to query if the cache is empty? – Jeff Evans Oct 29 '19 at 22:42
  • It's basically used as a map. I have a `Map>` and once a specific `Cache` value becomes empty I want to remove it from the `Map`. – Hanna Khalil Oct 29 '19 at 22:43
  • maybe https://stackoverflow.com/questions/3802370/java-time-based-map-cache-with-expiring-keys – Paul Rooney Oct 29 '19 at 22:48
  • The reason it's an approximation is because multiple threads can be modifying the cache at the same time. Can you say more about why you want to remove empty caches from the map? Is it for performance? Will it actually save you much? – Louis Wasserman Oct 29 '19 at 22:58
  • @LouisWasserman the reason I need to evict empty cache from the map is regarding the correctness of the app. – Hanna Khalil Oct 29 '19 at 23:17
  • Guava won't return expired entries, so usually how immediate the removal occurs is not important. Can you describe what "correctness of the app" entails? – Ben Manes Oct 31 '19 at 04:34

1 Answers1

4

You can use Guava for this purpose. Note the caveat about cleanup, as noted in the documentation here (reproduced below).

Caches built with CacheBuilder do not perform cleanup and evict values "automatically," or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare.

The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would need to create a thread, and its operations would be competing with user operations for shared locks. Additionally, some environments restrict the creation of threads, which would make CacheBuilder unusable in that environment.

Instead, we put the choice in your hands. If your cache is high-throughput, then you don't have to worry about performing cache maintenance to clean up expired entries and the like. If your cache does writes only rarely and you don't want cleanup to block cache reads, you may wish to create your own maintenance thread that calls Cache.cleanUp() at regular intervals.

If you want to schedule regular cache maintenance for a cache which only rarely has writes, just schedule the maintenance using ScheduledExecutorService.

As for the checking size point, you are correct that the size() is approximate. If you need to perform some action whenever an entry is invalidated, you should use the removalListener functionality. Relevant sample code from the documentation reproduced now.

CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
  public DatabaseConnection load(Key key) throws Exception {
    return openConnection(key);
  }
};
RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
  public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
    DatabaseConnection conn = removal.getValue();
    conn.close(); // tear down properly
  }
};

return CacheBuilder.newBuilder()
  .expireAfterWrite(2, TimeUnit.MINUTES)
  .removalListener(removalListener)
  .build(loader);
Jeff Evans
  • 1,257
  • 1
  • 15
  • 31
  • How will `removalListener ` allow me to know if cache is empty? – Hanna Khalil Oct 29 '19 at 22:51
  • Why do you need to know if the cache is empty? I don't think Guava can give such functionality, so if you require it for whatever reason, you will probably have to write your own. – Jeff Evans Oct 30 '19 at 18:24
  • 1
    Still, you could theoretically maintain your own counter. Increment it when an item is added to the cache, decrement when removed (via the listener). If it gets to 0, then your condition is met. – Jeff Evans Oct 30 '19 at 19:39
  • 1
    Counter is hard because it can be hard to determine when it should be incremented. A put may be a duplicate element. Instead you would need to maintain the set of keys and on removal notification remove the key (provided in the notification) from the set. Then if the set is empty, the cache is empty. Consider a class that wraps the cache (maybe extend ```ForwardingCache```) exposing get / put methods, implements ```RemovalListener``` and keeps track of the cached keys to determine empty state. – John B Nov 01 '19 at 18:33