排序ArrayList时如何解决ConcurrentModificationException异常
问题:调用ArrayList排序时随机触发ConcurrentModificationException异常
运行如下代码时随机触发ConcurrentModificationException异常:
fun sortAppListByLabelName() { Logger.debug("sortAppListByLabelName", "called sorting app list by label name") appsList.sortBy { it.label.toString() } }
其中appsList为ArrayList类型,定义如下:
private val appsList: ArrayList<AppInfo> = ArrayList()
抛出的异常栈信息如下:
Fatal Exception: java.util.ConcurrentModificationException at java.util.ArrayList.sort(ArrayList.java:1472) at java.util.Collections.sort(Collections.java:201) at r6.p.q(MutableCollectionsJVM.kt:1) at r6.j.q() at ****.****.****.sortAppListByLabelName(AppAdapter.kt:3) at ****.****.****.populateLauncherItems(Launcher.kt:18) at ****.****.****$populateAndRefreshCoroutine$1.invokeSuspend(Launcher.kt:4) at kotlin.coroutines.jvm.internal.a.resumeWith(ContinuationImpl.kt:3) at j7.o0.run(DispatchedTask.kt:22) at kotlinx.coroutines.scheduling.a.E(CoroutineScheduler.kt:1) at kotlinx.coroutines.scheduling.a$c.c(CoroutineScheduler.kt:4) at kotlinx.coroutines.scheduling.a$c.m(CoroutineScheduler.kt:4) at kotlinx.coroutines.scheduling.a$c.run(CoroutineScheduler.kt:1)
异常在Android 9、Android 11等多个Android版本均有崩溃记录,无法确定具体诱因:是排序逻辑在协程内执行导致?还是存在其他问题?
补充信息
能否补充你向ArrayList中添加元素的相关代码?
对应添加元素的代码如下:
fun addApp(app: AppInfo) { if(appsList.any{ it.label == app.label}) { VBLogger.debug("AppAdapter","addApp() NOT executed for ${app.label}") } else //add { appsList.add(app) VBLogger.debug("AppAdapter","addApp() executed for ${app.label}") } }
整体业务工作流如下:
suspend fun populateAppItems(){ val allApps: List<ResolveInfo?> = mPm.queryIntentActivities(i, PackageManager.MATCH_ALL) // 获取所有已安装应用,后续遍历处理 for (ri in allApps){ if(executingLauncherItemsAndRefreshNotifications!!.isActive) { // 调用addPackage方法,将单个应用添加到ListAdapter // 调用ListAdapter的addApp方法,即上述添加元素代码片段 } } // 调用sortAppListByLabelName方法,即上述排序代码片段 }
上述工作流由以下函数封装执行:
// 持有适配器更新协程任务,方便后续取消 private var executingAppItemsAndRefreshNotifications : Job? = null private fun populateAndRefreshCoroutine() { Logger.debug(TAG,"Entering populateAndRefreshCoroutine()") executingLauncherItemsAndRefreshNotifications?.cancel() // 取消正在运行的旧任务 executingAppItemsAndRefreshNotifications = launch() { populateAppItems() sendRefreshNotificationBroadcast() } }
根因分析
这个异常和协程本身没有直接关系,核心是**ArrayList本身是非线程安全容器,当前代码存在多线程/多协程并发修改同一个列表的场景**:
ArrayList内部维护了modCount字段记录列表的结构修改次数,增、删、排序这类会改变列表结构/顺序的操作都会更新这个值。所有遍历、排序操作执行前会记录当前的modCount,执行过程中如果发现这个值变了,就会直接抛出ConcurrentModificationException。- 启动协程时没有指定专属调度器,如果有其他地方从后台线程调用
addApp修改列表,和主线程/其他线程的排序操作同时执行,就会触发这个异常。 - 任务取消逻辑存在漏洞:调用
Job.cancel()只是给协程打取消标记,不会立刻终止已经在运行的代码。如果短时间多次触发populateAndRefreshCoroutine,旧协程还在循环执行addApp往列表加数据,新协程已经开始执行排序,两个协程同时操作同一个ArrayList,必然会触发modCount校验失败。 - 即使所有操作都在协程里,只要不同协程运行在不同的调度器(比如一个在主线程调度器、一个在IO线程池调度器),同时操作这个列表就会出问题。
修复方案
- 加锁保护所有列表操作:对
appsList的所有读写(包括addApp里的any遍历、add新增,以及sortBy排序)都加同一个对象锁,保证同一时间只有一个执行流能访问列表 - 替换为线程安全容器:用
Collections.synchronizedList()包装ArrayList,注意遍历、排序操作也要手动放在同步块内,否则依然会有并发问题 - 统一操作线程:所有对
appsList的修改、排序都调度到同一个单线程调度器执行,从调度层面避免并发 - 补全协程取消逻辑:启动新任务前,不仅要调用旧任务的
cancel(),还要调用join()等待旧任务完全执行结束,再启动新任务,避免新旧任务同时操作列表;也可以用协程的Mutex锁包裹所有列表操作,保证互斥访问
内容的提问来源于stack exchange,提问作者TiGer
相关产品推荐
相关产品推荐

