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.处理数据时,把数据转换为数组再进行操作