Java.util.ConcurrentModificationException

本文分析了在使用Gson进行序列化时遇到的ConcurrentModificationException异常原因,并提出了解决方案,包括移除待更改对象及其它预防措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java.util.ConcurrentModificationException

问题发生情景:

最近正在做数据采集相关工作,当使用gson.toJson时报错,此Crash不是100%复现,多次测试才可能出现:

E/2017-06-08 14:19:58.178 [pool-2-thread-102][CrashHandler] [TID:2676]uncaughtException msg = null
I/2017-06-08 14:19:58.178 [pool-2-thread-102][RunningRadioCrashHelper] [TID:2676] [tagRunningRecord] return false null null
E/2017-06-08 14:19:58.178 [pool-2-thread-102][CrashHandler] [TID:2676]不好,发生了异常 uncaughtException
* Throwable : 
java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
    at com.elvis.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.elvis.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
    at com.elvis.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
    at com.elvis.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:126)
    at com.elvis.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:244)
    at com.elvis.gson.Gson.toJson(Gson.java:671)
    at com.elvis.gson.Gson.toJson(Gson.java:650)
    at com.elvis.gson.Gson.toJson(Gson.java:605)
    at com.elvis.gson.Gson.toJson(Gson.java:585)
    at com.utest.pdm.PDMFileManager.appendPageLoadInfo(PDMFileManager.java:274)
    at com.utest.pdm.monitor.pageload.PageLoadMonitor$1$1.run(PageLoadMonitor.java:161)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)

问题分析与解决:

1.查阅相关资料:

An ConcurrentModificationException is thrown when a Collection is modified and an existing iterator on the Collection is used to modify the Collection as well.—-大致意思是:ConcurrentModificationException抛出的条件是,一个迭代器在迭代集合的时候,集合被修改了

2.分析问题栈:

发现问题根源是at java.util.ArrayList执行next()函数时抛出异常,所以猜测问题的大概原因是Gson对ArrayList对象进行序列化时,ArrayList对象长度进行了更改。跟踪上层代码,代码中我们开了一个新的线程去对对象进行序列化,并且pageLoadInfo对象在父线程中还有更改的可能性。这就是问题的所在:

//保存onPause信息,倒序遍历,赋值给第一个页面(原理和 @addDrawInfo 方法类似)
for (int i = pageList.size() - 1; i >= 0; i--) {
    final PageLoadInfo pageLoadInfo = pageList.get(i);
    if (pageLoadInfo.activityClassName.equals(activityClassName) && pageLoadInfo.objectHashCode.equals(objectHashCode)) {
        pageLoadInfo.addPauseInfo(start, end);
        ThreadPoolUtil.execute(new Runnable() {
            @Override
            public void run() {
                PDMFileManager.getInstance().appendPageLoadInfo(pageLoadInfo);
            }
        });
        break;
    }
}

3.问题解决方案:

于是我们针对业务的需求,可以将pageLoadInfo对象移除更改队列,让此对象不能再被更改。修复后的代码:

//保存onPause信息,倒序遍历,赋值给第一个页面(原理和 @addDrawInfo 方法类似)
for (int i = pageList.size() - 1; i >= 0; i--) {
    final PageLoadInfo pageLoadInfo = pageList.get(i);
    if (pageLoadInfo.activityClassName.equals(activityClassName) && pageLoadInfo.objectHashCode.equals(objectHashCode)) {
        pageLoadInfo.addPauseInfo(start, end);
        //移除页面列表
        pageList.remove(pageLoadInfo);
        //本地文件保存页面信息
        ThreadPoolUtil.execute(new Runnable() {
            @Override
            public void run() {
                PDMFileManager.getInstance().appendPageLoadInfo(pageLoadInfo);
            }
        });
        break;
    }
}

总结:

Java.util.ConcurrentModificationException异常一定是由于对象在备操作的过程中被更改所导致的,本文所出现的问题是由于多线程同时操作同一对象导致的,查阅网上资料发现:使用List、Set、Map 时通过Iterator进行遍历,并通过集合的remove(Object) 进行删除操作,都可能引起此异常。详细请参考这篇文章: [Java ConcurrentModificationException 异常分析与解决方案]

解决问题的方法也都同一个原理,防止对象被同时操作:

  1.使用Iterator提供的remove方法,用于删除当前元素

  2.建一个集合,记录需要删除的元素,之后统一删除

  3.使用线程安全CopyOnWriteArrayList进行删除操作

  4.不使用Iterator进行遍历,需要注意的是自己保证索引正常

  5.处理数据时,把数据转换为数组再进行操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值