From When Does Cleanup Happen? · CachesExplained · google/guava Wiki:
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 such, if you are only doing reads you "might" be good if you do a Cache.cleanUp()
just before and after your transaction but there is still no guarantee.
However, instead of trying to force items to stay in the cache you might instead simply evict items to another cache/map using a removalListener
and then when you read you will first need to check the cache and then, if it wasn't there, check the items evicted during the long-running transaction.
The following is an oversimplified example:
Map<Integer, String> evicted = new ConcurrentHashMap<>();
Cache<Integer, String> cache = CacheBuilder.newBuilder()
.expireAfterAccess(2, SECONDS)
.removalListener((RemovalListener<Integer, String>) notification ->
evicted.put(notification.getKey(), notification.getValue()))
.build();
assert evicted.size() == 0 && cache.size() == 0;
cache.put(0, "a");
cache.put(1, "b");
cache.put(2, "c");
assert evicted.size() == 0 && cache.size() == 3;
sleepUninterruptibly(1, SECONDS);
assert evicted.size() == 0 && cache.size() == 3;
cache.put(3, "d");
assert evicted.size() == 0 && cache.size() == 4;
sleepUninterruptibly(1, SECONDS);
cache.cleanUp();
assert evicted.size() == 3 && cache.size() == 1;
Integer key = 2;
String value;
{
value = cache.getIfPresent(key);
if (value == null) value = evicted.get(key);
}
assert Objects.equals(value, "c");
Your actual code would need to conditionally put
into evicted
, clean-up evicted
, manage multiple evicted
objects if your running long-running transactions concurrently or use a common cache between the threads with a different eviction strategy, etc. but hopefully this demonstrates the idea sufficiently to get you started.