我的代码上不断出现并发修改异常。我只是迭代一个hashmap并修改值。从研究这个我发现人们说使用迭代器和iterator.remove等我尝试用这个实现并仍然得到错误。我想也许有多个线程可以访问它? (虽然在我的代码中,这个块只在一个线程中运行)所以我把它放在一个synchronized块中。但是,我仍然得到错误.....
Map map= Collections.synchronizedMap(questionNumberAnswerCache);
synchronized (map) {
for (Iterator<Map.Entry<String, Integer>> it = questionNumberAnswerCache.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getKey() == null || entry.getValue() == null) {
continue;
} else {
try {
Question me = Question.getQuery().get(entry.getKey());
int i = Activity.getQuery()
.whereGreaterThan(Constants.kQollegeActivityCreatedAtKey, lastUpdated.get("AnswerNumberCache " + entry.getKey()))
.whereEqualTo(Constants.kQollegeActivityTypeKey, Constants.kQollegeActivityTypeAnswer)
.whereEqualTo(Constants.kQollegeActivityQuestionKey, me)
.find().size();
lastUpdated.put("AnswerNumberCache " + entry.getKey(), Calendar.getInstance().getTime());
int old_num = entry.getValue();
entry.setValue(i + old_num);
} catch (ParseException e) {
entry.setValue(0);
}
}
}
}
错误:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:787)
at java.util.HashMap$EntryIterator.next(HashMap.java:824)
at java.util.HashMap$EntryIterator.next(HashMap.java:822)
at com.juryroom.qollege_android_v1.QollegeCache.refreshQuestionAnswerNumberCache(QollegeCache.java:379)
at com.juryroom.qollege_android_v1.QollegeCache.refreshQuestionCaches(QollegeCache.java:267)
at com.juryroom.qollege_android_v1.UpdateCacheService.onHandleIntent(UpdateCacheService.java:28)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.os.HandlerThread.run(HandlerThread.java:61)
答案 0 :(得分:2)
发生了什么:
迭代器循环遍历地图。地图并不像列表,因为它并不关心订单。因此,当您向地图添加内容时,它可能会插入到中间,在您已经循环的对象中间的某个位置,最后等等。因此,它不会给您随机行为,而是失败。
您的解决方案:
同步映射和同步块允许您同时使用两个线程。它在这里并没有真正帮助,因为问题是同一个线程正以非法的方式修改它。
你应该做什么:
您可以保存要修改的密钥。制作带有键和新值的地图不会成为问题,除非这是一个非常重要的时间代码。
然后,您只需遍历newValues映射并更新oldValues映射。由于您没有迭代正在更新的地图,因此不存在问题。
或者您可以简单地遍历键(对于String s:yourMap),然后查找要更改的值。由于您只是遍历键,因此您可以自由更改值(但您无法删除值)。
您也可以尝试使用ConcurrentHashMap,它允许您修改它,但行为是未定义的,因此这是有风险的。只是改变值不应该导致问题,但是如果你添加或删除你永远不知道它是否会最终被迭代通过。
答案 1 :(得分:1)
创建一个物体,并锁定它 - 一个用脚射击自己的好方法。
我建议使用以下代码删除哈希映射。
HashMap<Key, Object> hashMap = new HashMap<>();
LinkedList<Key> listToRemove = new LinkedList<>();
for(Map.Entry<Key, Object> s : hashMap.entrySet()) {
if(s.getValue().equals("ToDelete")){
listToRemove.add(s.getKey());
}
}
for(Key s : listToRemove) {
hashMap.remove(s);
}
这不是最美丽,最快捷的选择,但它可以帮助您了解如何使用HashMap。
您将了解如何使用我的选项。您可以了解how to work iterators,how to work iterators in loop。 (而不是简单地复制粘贴)
Iterator it = tokenMap.keySet())
while(it.hasNext()) {
if(/* some condition */) it.remove();
}
答案 2 :(得分:1)
我建议您使用以下内容:
for(Key key : hashMap.keySet()) {
Object value = hashMap.get(key);
if(<condition>){
hashMap.put(key, <new value>);
}
如果您没有删除任何条目而只是更改值,这应该适合您。