Java从ConcurrentHashMap中删除特定项

10

使用 remove() 方法是否可以?我看过一篇文章,说从 remove() 方法中没有加入同步。那么我应该如何正确地从 ConcurrentHashMap 中移除一个指定的项?

示例代码:

    ConcurrentHashMap<String,Integer> storage = new ConcurrentHashMap<String,Integer>();
    storage.put("First", 1);
    storage.put("Second", 2);
    storage.put("Third",3);


    //Is this the proper way of removing a specific item from a tread-safe collection?
    storage.remove("First");

    for (Entry<String, Integer> entry : storage.entrySet()) {
        String key = entry.getKey();
        Object value = entry.getValue();
        // ...
        System.out.println(key + " " + value);
    }

我读过一篇文章,说删除方法中没有添加同步功能。这篇文章是关于Java 5、6、7还是8的?也许它是以更早的Java版本为基础写的。 - peter.petrov
@peter.petrov - 在我的代码示例中调用ConcurrentHashMap上的remove方法是可以的,对吧?线程安全不应该是一个问题吧? - user4350786
不是很确定。我猜这篇文章的意思是:在remove中,锁定的不是整个表,而只是其中的一个给定段(正如manouti发布的代码所示)。至少这就是我对文章及其含义的理解。因此,一切都取决于您的并发情况。不确定,如果我要确信地回答这个问题,我需要从头开始进行研究。 - peter.petrov
@peter.petrov - 嗯,那么我该如何从ConcurrentHashMap中删除特定的项? - user4350786
@peter.petrov - manouti 确认它没问题。 - user4350786
显示剩余4条评论
3个回答

4

迭代器应该可以胜任:

Iterator<Map.Entry<String, Integer>> iterator = storage.entrySet().iterator();
while(iterator.hasNext())
{
    Map.Entry<String, Integer> entry = iterator.next();
    if(entry.getKey().equals("First"))
    {
       iterator.remove();
    }
 }

参考资料:https://dzone.com/articles/removing-entries-hashmap

2

remove 方法确实需要同步锁。事实上,在检查 ConcurrentHashMap#remove() 代码时,会发现它调用了一个 lock 方法以获取锁:

public V remove(Object key) {
    int hash = hash(key.hashCode());
    return segmentFor(hash).remove(key, hash, null);
}

ConcurrentHashMap.Segment#remove(key, hash, null)定义如下:

V remove(Object key, int hash, Object value) {
     lock();
     try {
        ...

注意Javadoc描述:
检索操作(包括get)通常不会阻止,因此可能与更新操作(包括putremove)重叠。检索反映了最近一次完成的更新操作的结果,在其开始时保持不变。对于像putAllclear这样的聚合操作,同时进行的检索可能只反映了某些条目的插入或删除。同样,迭代器和枚举返回反映哈希表状态的元素,该状态是迭代器/枚举创建后的某个时间点。它们不会抛出ConcurrentModificationException。但是,迭代器只设计为由一个线程使用。

1
没问题。它是线程安全的,使用它不需要锁定,也不会出现“ConcurrentModificationException”。 - M A
1
再说一遍:作者只是说remove方法在整个表对象上不是同步的。至少这是我的理解。 - peter.petrov
1
@peter.petrov - 只是为了确认一下,我会放弃这种情况,我在上面的示例代码中调用的方式是正确的,我不必担心线程安全吗? - user4350786
1
@spudone 是的,这里没有矛盾。它们是线程安全的,正在讨论的是一致性水平(请参见mahouti发布的javadoc)。例如,在某些复杂场景中,get操作可能会看到表中已经被移除的内容:) 这就是我理解所有这些的方式。想象一下与RDBMS和隔离级别相关的类比。希望您明白我的意思。 - peter.petrov
“在锁上同步”是个自相矛盾的说法。ConcurrentHashMap 中没有同步,但有锁,这些锁可以被锁定解锁 - user207421
显示剩余7条评论

1
您可以直接在entrySet上使用removeIf:
map.entrySet().removeIf( entry -> .. some condicion on entry ) 

请注意,Java 8存在一个错误,在Java 9中才得到修复( 这里 )。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接