Android: Hashmap并发修改异常
Android: Hashmap concurrent Modification Exception
我的代码不断出现并发修改异常。我只是遍历哈希图并修改值。通过研究,我发现有人说要使用迭代器和 iterator.remove 等。我尝试用它来实现,但仍然不断出现错误。我想也许多个线程访问它? (虽然在我的代码中这个块在一个线程中只有运行)所以我把它放在一个同步块中。但是,我仍然收到错误......
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)
发生了什么:
迭代器正在遍历地图。地图并不像列表,因为它不关心顺序。因此,当您将某些内容添加到地图时,它可能会插入到中间,在您已经循环遍历的对象中间的某个位置,在最后等等。因此,它不会给您随机行为,而是会失败。
您的解决方案:
同步映射和同步块允许您同时处理两个线程。它在这里并没有真正的帮助,因为问题是同一个线程正在以非法方式修改它。
你应该做什么:
您可以只保存要修改的键。使用键和新值制作地图不会有问题,除非这是一段真正时间紧迫的代码。
然后您只需遍历 newValues 映射并更新 oldValues 映射。由于您没有遍历正在更新的地图,所以这不是问题。
或者您可以简单地遍历键(对于 String s : yourMap),然后查找您想要更改的值。由于您只是遍历键,因此您可以自由更改值(但不能删除值)。
您也可以尝试使用 ConcurrentHashMap,它应该允许您对其进行修改,但行为未定义,因此这是有风险的。仅仅改变值不应该导致问题,但是如果你添加或删除你永远不知道它是否最终会被迭代。
创建一个对象,并锁定到它 - 搬起石头砸自己脚的好方法。
我推荐以下代码来删除哈希映射。
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();
}
我会针对您的用例提出以下建议:
for(Key key : hashMap.keySet()) {
Object value = hashMap.get(key);
if(<condition>){
hashMap.put(key, <new value>);
}
如果您不删除任何条目而只是更改值,这应该适合您。
我的代码不断出现并发修改异常。我只是遍历哈希图并修改值。通过研究,我发现有人说要使用迭代器和 iterator.remove 等。我尝试用它来实现,但仍然不断出现错误。我想也许多个线程访问它? (虽然在我的代码中这个块在一个线程中只有运行)所以我把它放在一个同步块中。但是,我仍然收到错误......
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)
发生了什么:
迭代器正在遍历地图。地图并不像列表,因为它不关心顺序。因此,当您将某些内容添加到地图时,它可能会插入到中间,在您已经循环遍历的对象中间的某个位置,在最后等等。因此,它不会给您随机行为,而是会失败。
您的解决方案:
同步映射和同步块允许您同时处理两个线程。它在这里并没有真正的帮助,因为问题是同一个线程正在以非法方式修改它。
你应该做什么:
您可以只保存要修改的键。使用键和新值制作地图不会有问题,除非这是一段真正时间紧迫的代码。
然后您只需遍历 newValues 映射并更新 oldValues 映射。由于您没有遍历正在更新的地图,所以这不是问题。
或者您可以简单地遍历键(对于 String s : yourMap),然后查找您想要更改的值。由于您只是遍历键,因此您可以自由更改值(但不能删除值)。
您也可以尝试使用 ConcurrentHashMap,它应该允许您对其进行修改,但行为未定义,因此这是有风险的。仅仅改变值不应该导致问题,但是如果你添加或删除你永远不知道它是否最终会被迭代。
创建一个对象,并锁定到它 - 搬起石头砸自己脚的好方法。
我推荐以下代码来删除哈希映射。
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();
}
我会针对您的用例提出以下建议:
for(Key key : hashMap.keySet()) {
Object value = hashMap.get(key);
if(<condition>){
hashMap.put(key, <new value>);
}
如果您不删除任何条目而只是更改值,这应该适合您。