ConcurrentLinkedHashMap.Builder的删除与get处理及删除后get的线程安全问题
Great question! Let's dive into how ConcurrentLinkedHashMap.Builder handles delete and get operations, especially the race condition you're worried about in your code.
First, Key Thread-Safety Basics
ConcurrentLinkedHashMap is built on top of ConcurrentHashMap, so all its core operations (get, remove, containsKey) are thread-safe and atomic—no extra synchronization is needed from your side. But there's a catch when you combine containsKey and get like you did, which we'll cover next.
The Race Condition in Your Code
In your getEntity method:
if (entityLRUCache.containsKey(entityId)) { // Another thread could delete entityId here! Entity entity = entityLRUCache.get(entityId); // entity might be null now }
The problem is that containsKey and get are two separate operations. Even if containsKey returns true, another thread can delete the key in the split second between those two calls. When you then call get, it will return null—a classic race condition.
How Delete and Get Operations Work Internally
Let's break down what happens under the hood:
- Delete (remove) operation: This is an atomic action that removes the entry from both the underlying concurrent hash table and the LRU linked list. The operation uses CAS (Compare-and-Swap) and lock-free mechanisms to ensure thread safety without blocking other operations. Once the delete completes, no subsequent
getcalls will find the key. - Get operation: This is also atomic. It checks the hash table for the key:
- If found, it updates the LRU linked list (moving the entry to the head to mark it as recently used) and returns the value.
- If not found, it returns
nullimmediately.
Crucially, get and remove operations don't block each other. A get might briefly see a stale value if a remove is in progress (due to ConcurrentHashMap's weak consistency), but once the remove finishes, all subsequent gets will correctly return null.
Fixing Your Code
The simplest way to eliminate the race condition is to replace the containsKey + get pair with a single get call:
Entity getEntity(GUID entityId) { Entity entity = entityLRUCache.get(entityId); if (entity != null) { // Handle existing entity } else { // Handle missing entity (maybe load it from DB, etc.) } }
This works because get is atomic—there's no window for another thread to delete the key between checking existence and retrieving the value.
Additional Notes for LRU Cache Usage
- When you delete a key, it's fully removed from the LRU eviction pool, so it won't interfere with future eviction decisions.
- If you need to perform a conditional delete (e.g., only delete if the value matches a certain state), use
remove(Object key, Object value)which is also atomic.
内容的提问来源于stack exchange,提问作者Alex Kokorin

