Search code examples
javamultithreadingconcurrenthashmap

Iterate over ConcurrentHashMap while deleting entries


I want to periodically iterate over a ConcurrentHashMap while removing entries, like this:

for (Iterator<Entry<Integer, Integer>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
    Entry<Integer, Integer> entry = iter.next();
    // do something
    iter.remove();
}

The problem is that another thread may be updating or modifying values while I'm iterating. If that happens, those updates can be lost forever, because my thread only sees stale values while iterating, but the remove() will delete the live entry.

After some consideration, I came up with this workaround:

map.forEach((key, value) -> {
    // delete if value is up to date, otherwise leave for next round
    if (map.remove(key, value)) {
        // do something
    }
});

One problem with this is that it won't catch modifications to mutable values that don't implement equals() (such as AtomicInteger). Is there a better way to safely remove with concurrent modifications?


Solution

  • Your workaround works but there is one potential scenario. If certain entries have constant updates map.remove(key,value) may never return true until updates are over.

    If you use JDK8 here is my solution

    for (Iterator<Entry<Integer, Integer>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
        Entry<Integer, Integer> entry = iter.next();
        Map.compute(entry.getKey(), (k, v) -> f(v));
        //do something for prevValue
    }
    ....
    private Integer prevValue;
    
    private Integer f(Integer v){
        prevValue = v;
        return null;
    }
    

    compute() will apply f(v) to the value and in our case assign the value to the global variable and remove the entry.

    According to Javadoc it is atomic.

    Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this Map.