You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

排序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本身是非线程安全容器,当前代码存在多线程/多协程并发修改同一个列表的场景**:

  1. ArrayList内部维护了modCount字段记录列表的结构修改次数,增、删、排序这类会改变列表结构/顺序的操作都会更新这个值。所有遍历、排序操作执行前会记录当前的modCount,执行过程中如果发现这个值变了,就会直接抛出ConcurrentModificationException。
  2. 启动协程时没有指定专属调度器,如果有其他地方从后台线程调用addApp修改列表,和主线程/其他线程的排序操作同时执行,就会触发这个异常。
  3. 任务取消逻辑存在漏洞:调用Job.cancel()只是给协程打取消标记,不会立刻终止已经在运行的代码。如果短时间多次触发populateAndRefreshCoroutine,旧协程还在循环执行addApp往列表加数据,新协程已经开始执行排序,两个协程同时操作同一个ArrayList,必然会触发modCount校验失败。
  4. 即使所有操作都在协程里,只要不同协程运行在不同的调度器(比如一个在主线程调度器、一个在IO线程池调度器),同时操作这个列表就会出问题。

修复方案

  • 加锁保护所有列表操作:对appsList的所有读写(包括addApp里的any遍历、add新增,以及sortBy排序)都加同一个对象锁,保证同一时间只有一个执行流能访问列表
  • 替换为线程安全容器:用Collections.synchronizedList()包装ArrayList,注意遍历、排序操作也要手动放在同步块内,否则依然会有并发问题
  • 统一操作线程:所有对appsList的修改、排序都调度到同一个单线程调度器执行,从调度层面避免并发
  • 补全协程取消逻辑:启动新任务前,不仅要调用旧任务的cancel(),还要调用join()等待旧任务完全执行结束,再启动新任务,避免新旧任务同时操作列表;也可以用协程的Mutex锁包裹所有列表操作,保证互斥访问

内容的提问来源于stack exchange,提问作者TiGer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:21:34