Search code examples
javadata-structuresconcurrencyconcurrenthashmap

How does ConcurrentLinkedHashMap.Builder handles deletions and get?


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


Solution

  • 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);
    }