【问题标题】:Android - ConcurrentModificationException - Read and remove in different threadsAndroid - ConcurrentModificationException - 在不同的线程中读取和删除
【发布时间】:2015-06-17 10:13:25
【问题描述】:

我在使用线程和 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 成员字段。
  • Job 是一个至少有一个属性是 HashMap 的类。
  • 好吧,如果您要跨线程与该映射进行交互,那就有问题了 - HashMap 不是线程安全的。

标签: java android multithreading hashmap gson


【解决方案1】:

如果您查看堆栈跟踪,您会看到 HashMap 在 Gson 对其进行迭代时同时被修改(注意 com.google.gsonHashMap$EntryIterator.next 之前的调用。

DataFragment.java 的第 825 行看起来,您正在将一个包含 HashMap 的对象(Job,我假设?)传递给 Gson,但您同时在更新 HashMap单独的线程。您可能只需要找到HashMapJob 中的声明位置,并将其更改为ConcurrentHashMap,它不会抛出ConcurrentModificationException

您无法永远安全地使用HashMap 或跨多个线程的其他非线程安全对象。您的选择是 a) 使用线程安全的数据结构或 b) 使用外部同步或锁定机制来确保线程安全。

【讨论】:

    【解决方案2】:

    使用java迭代器避免并发修改异常。

    Map<Long, DataItem> dataItemQueue  = new HashMap<>();
    Iterator iterator = dataItemQueue.entrySet().iterator();
    while (iterator.hasNext()){
        if( /* your condition goes here */ ){
            iterator.remove();
        }
    }
    

    【讨论】:

    • 在修改为删除时有效。如果您改为添加或替换元素,则此策略是不够的。此外,如果跨多个线程访问 HashMap,即使 Iterator.remove() 也不安全 - 它可能不会触发 CME,但仍可能导致不一致或损坏的行为。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 2014-07-15
    • 2016-11-05
    • 1970-01-01
    • 2020-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多