Android - ConcurrentModificationException - 不同线程中读取和删除问题

4
我在使用线程和HashMap时遇到了ConcurrentModificationException问题。如果可能的话,我希望能够得到一些关于同时读取和更改HashMap的想法。或者最好的解决方案是将线程排队等待执行吗?
HashMap示例:
protected final ConcurrentHashMap<Long, DataItem> dataItemQueue = new ConcurrentHashMap<Long, RegisterStorageLocationQueueItem>();

线程的基本作用:

Job job = new Job(var1, var2, dataItemQueue);
Bundle bundle = new Bundle();
bundle.putSerializable("job", new Gson().toJson(job));
// Saving/sending the serialized data.
storeItemData();

在该线程运行时,我处理“数据项”并将其逐个从HashMap中删除。有时会出现这个异常,我猜测是在使用Gson序列化期间尝试从HashMap中删除项目时发生的。我运行线程加快整个过程的速度,因为在线程中完成的工作可能需要2-4秒钟,我不想锁定主线程。
我尝试通过使用ConcurrenthashMap并在线程内部克隆HashMap来解决此问题,以使其不会锁定Hashmap,如下所示,但是我还没有找到一个可行的解决方案。
ConcurrentHashMap<Long, DataItem> newDataItemQueue = new ConcurrentHashMap<Long, DataItem>();
for (Entry<Long, DataItem> entry : this.dataItemQueue.entrySet()) {
    newDataItemQueue.put(entry.getKey(), new DataItem(entry.getValue()));
}

堆栈跟踪:

java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:792)
at java.util.HashMap$EntryIterator.next(HashMap.java:829)
at java.util.HashMap$EntryIterator.next(HashMap.java:827)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:206)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:99)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:219)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:99)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:219)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:208)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:99)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:219)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:99)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:219)
at com.google.gson.Gson.toJson(Gson.java:600)
at com.google.gson.Gson.toJson(Gson.java:579)
at com.google.gson.Gson.toJson(Gson.java:534)
at com.google.gson.Gson.toJson(Gson.java:514)
at com.asd.admin.fragments.DataFragment.saveStateData(DataFragmentment.java:825)

你能分享一下“Job”的定义吗?它似乎可能(间接地)有一个HashMap成员字段。 - dimo414
Job是一个至少有一个属性为HashMap的类。 - Jimmy Garpehäll
如果您在不同的线程中与该映射进行交互,那么这是一个问题 - HashMap 不是线程安全的。 - dimo414
2个回答

1
如果您查看堆栈跟踪,您会发现在Gson迭代时正在同时修改HashMap(请注意HashMap$EntryIterator.next之前的com.google.gson调用)。
从看起来像是在DataFragment.java的第825行中,您正在将一个包含HashMap的对象(我猜是Job?)传递给Gson,但您正在另一个线程中并发更新该HashMap。您可能只需要找到在Job内部声明这个HashMap的位置,并将其更改为ConcurrentHashMap,这样就不会抛出ConcurrentModificationException
您不能安全地在多个线程之间使用HashMap或其他非线程安全的对象。您的选择是a)使用线程安全的数据结构或b)使用外部同步或锁定机制来确保线程安全。

-1
使用Java迭代器来避免并发修改异常。
Map<Long, DataItem> dataItemQueue  = new HashMap<>();
Iterator iterator = dataItemQueue.entrySet().iterator();
while (iterator.hasNext()){
    if( /* your condition goes here */ ){
        iterator.remove();
    }
}

这仅在修改是删除时才有效。如果您要添加或替换元素,则此策略不足。此外,即使使用Iterator.remove(),如果正在跨多个线程访问HashMap,它也不安全-可能不会触发CME,但仍可能导致不一致或破损的行为。 - dimo414

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