如何避免TreeMap的ConcurrentModificationException异常?

14

我正在调用一个返回TreeMap实例的函数,而在调用代码中,我想要修改这个TreeMap。但是,我却得到了一个ConcurrentModificationException

以下是我的代码:

public Map<String, String> function1() {
    Map<String, String> key_values = Collections.synchronizedMap(new TreeMap<String, String>());
    // all key_values.put() goes here

    return key_values;
}

我的国际区号是:

Map<String, String> key_values =Collections.synchronizedMap(Classname.function1());
//here key_values.put() giving ConcurrentModificationException

2
我可以问一下在function1内部创建同步Map的目的吗?它没有被任何人使用,只有你的"调用代码"使用它... - Alessandro Santini
你如何修改地图? - rai.skumar
5个回答

16
请注意,如果使用迭代器,Collections.synchronizedMap 将无法保护您免受并发修改的影响。此外,除非您从多个线程访问Map,否则创建同步地图是没有意义的。局部范围的集合和不被传递给其他线程的变量不需要进行synchronized 同步。 我猜测在您留下的代码中,您正在迭代其中一个Map.entrySetMap.keySetMap.values,并且在迭代过程中(在for循环内)调用了put方法。根据您展示的代码,这是唯一可能发生的情况。

嗯,你说得对,先生,那是我的错误。谢谢你。 - Nagappa L M

7
如果您使用ConcurrentSkipListMap,它可能会更快,并且不会出现此问题。
public NavigableMap<String, String> function1() {
    NavigableMap<String, String> key_values = new ConcurrentSkipListMap<String, String>();
    // all key_values.put() goes here

    return key_values;
}

如果您不需要对键进行排序,可以使用ConcurrentHashMap

2
了解什么是跳表可能会很有用:https://zh.wikipedia.org/wiki/%E8%B7%B3%E8%A1%A8 - MartyIX
1
如果您正在使用compute...merge方法之一,请注意Javadoc中的以下注释:不保证函数会被原子地应用。这可能是一个令人困惑的说明;但据我所知,它意味着逻辑必须是幂等的(因为它可能会重复)。ConcurrentHashMap提供了更强的保证:您的逻辑将被计算一次。 - Robert
@Robert 如果你在compute函数内修改map,那么CHM可能会出现死锁。其他的Map可以有重复的键值(即键出现多次)。简而言之,不要这样做。 - Peter Lawrey

1

您似乎正在获取一个同步映射的同步映射。如果我用它的内容(简化版)替换对function1()的调用,我们有:

Map<String, String> key_values =Collections.synchronizedMap(Collections.synchronizedMap( new TreeMap<String, String>()));

我认为你的呼叫线路应该更改为:

Map<String, String> key_values = Classname.function1();

@Brian 我认为这个细节很重要。因为如果使用双重包装,他将无法在实际的TreeMap对象上进行同步,而如果他尝试迭代和更新,这是必需的。 - Vamsi Mohan Jayanti

1

您正在寻找一个同步映射表,因此我假设您正在处理多线程应用程序。在这种情况下,如果您想使用迭代器,则必须为映射表同步块。

/*This reference will give error if you update the map after synchronizing values.*/
    Map<String, String> values =Collections.synchronizedMap(function1());

/*This reference will not give error if you update the map after synchronizing values  */
        Map<String, String> values = Collections.synchronizedMap(new TreeMap<String, String>());


     synchronized (values) 
                {           
                    Iterator it = values.entrySet().iterator();
                    while(it.hasNext())
                    {
                        it.next() ; 
    // You can update the map here.     
                    }
                }

更新:

实际上,在您的情况下,考虑到您两次包装MAP时出现的错误,即使在while循环中使用同步块进行修改,也会导致CM异常,因为您将无法对正在更新的原始MAP对象进行同步。


0
这是我做的事情,导致了 ConcurrentModificationException 异常的发生:
TreeMap<Integer, Integer> map = new TreeMap<>();

for (Integer i : map.keySet()) {
    map.remove(i)
}

这是我为了修复异常而实现相同功能所做的:

TreeMap<Integer, Integer> map = new TreeMap<>();

// I would not recommend this in production as it will create multiple
// copies of map
for (Integer i : new HashMap<>(integers).keySet()) {
    map.remove(i)
}

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