I use ConcurrentLinkedHashMap as LRUCache
and I'm curious how it handles .get
after deletion
of a key (because we'll eventually have to remove keys from LRUCache
due to its policy.
entityLRUCache = new ConcurrentLinkedHashMap.Builder<GUID, Entity>()
.maximumWeightedCapacity(100)
.build();
...
Entity getEntity(GUID entityId)
{
if (entityLRUCache.containsKey(entityId))
{
// Question: what if key gets deleted from other
// thread (when we jumped into this if statement)
// and then we'll try to retrieve it here using .get()
return entityLRUCache.get(entityId);
}
else
{
Entity entity = longLoadFromDatabase(entityId);
entityLRUCache.put(entityId, entity);
return entity;
}
}
How can I handle these type of situations with this ConcurrentLinkedHashMap
class?
Thanks
In this case, you would would want to avoid reading multiple times from the cache to avoid race conditions. Instead you would write this as,
Entity getEntity(GUID entityId) {
Entity entity = entityLRUCache.get(entityId);
if (entity == null) {
entity = longLoadFromDatabase(entityId);
entityLRUCache.put(entityId, entity);
}
return entity;
}
This has a race, called a cache stampede, when loading the value to populate on a miss. For that library, one might write a decorator using lock striping or storing futures to avoid this if problematic. The Google Code wiki used to provide an example of how to write a SelfPopulatingMap.
ConcurrentLinkedHashMap
merged into Guava and evolved into Caffeine. You should prefer that library, where you could write this as,
Entity getEntity(GUID entityId) {
return entityCache.get(entityId, this::longLoadFromDatabase);
}