摘要:在Kotlin开发中,ConcurrentModificationException(并发修改异常)是让开发者头疼的“老熟人”。无论是单线程遍历时修改集合,还是多线程并发操作集合,这个异常都可能突然出现,打断程序的正常运行。本文将深入剖析该异常的根源,并结合K
在Kotlin开发中,ConcurrentModificationException(并发修改异常)是让开发者头疼的“老熟人”。无论是单线程遍历时修改集合,还是多线程并发操作集合,这个异常都可能突然出现,打断程序的正常运行。本文将深入剖析该异常的根源,并结合Kotlin特性提供解决方案。
在java/Kotlin的集合框架中,ArrayList等非线程安全集合通过modCount(修改计数器)和expectedModCount(预期修改计数器)维护结构一致性。
modCount:记录集合的结构性修改次数(如add、remove)。expectedModCount:迭代器创建时复制的modCount快照,用于校验迭代期间集合是否被修改。当迭代器检测到modCount != expectedModCount时,会抛出ConcurrentModificationException。例如:
val list = mutableListOf("A", "B", "C")for (item in list) { if (item == "B") { list.remove("B") // 直接修改集合,触发异常 } }单线程遍历中修改集合Kotlin的增强型for循环(for (item in list))底层依赖Java迭代器。若在遍历时直接调用集合的add/remove方法,会修改modCount,导致expectedModCount失效。多线程并发修改
多个线程同时操作同一集合时,若一个线程在遍历,另一个线程修改集合,会引发异常。例如:val list = mutableListOf(1, 2, 3)thread { for (item in list) { Thread.sleep(100) // 模拟耗时操作 println(item) } }thread { repeat(3) { list.add(it + 4) Thread.sleep(50) } }隐式调用迭代器的场景toString方法(如ArrayList)可能隐式使用迭代器。Kotlin的mapIndexed、filter等高阶函数若操作可变集合,也可能触发异常。
Kotlin的迭代器支持remove操作,且会同步更新expectedModCount,避免异常:
val list = mutableListOf("A", "B", "C")val iterator = list.iteratorwhile (iterator.hasNext) { val item = iterator.next if (item == "B") { iterator.remove // 安全删除 } }println(list) // 输出: [A, C]对于读多写少的场景,CopyOnWriteArrayList是最佳选择。它通过写时复制机制保证线程安全:
val list = java.util.concurrent.CopyOnWriteArrayListlist.addAll(listOf("A", "B", "C"))for (item in list) { if (item == "B") { list.remove("B") // 不会抛出异常 } }println(list) // 输出: [A, C]通过synchronized块或ReentrantLock显式加锁,确保同一时间只有一个线程操作集合:
val list = mutableListOf("A", "B", "C")synchronized(list) { for (item in list) { if (item == "B") { list.remove("B") } } }println(list) // 输出: [A, C]先遍历集合收集需要修改的元素,再统一操作:
val list = mutableListOf("A", "B", "C")val toRemove = mutableListOffor (item in list) { if (item == "B") { toRemove.add(item) } }list.removeAll(toRemove)println(list) // 输出: [A, C]协程与withContext(Dispatchers.Default)在协程中,通过切换到Dispatchers.Default(共享线程池)并使用synchronized,可高效处理并发修改:val list = mutableListOf(1, 2, 3)val job = GlobalScope.launch { withContext(Dispatchers.Default) { synchronized(list) { repeat(3) { list.add(it + 4) delay(50) } } } }Sequence避免中间集合
使用Sequence延迟计算,减少临时集合的创建:val list = mutableListOf(1, 2, 3, 4, 5)val result = list.asSequence.filter { it % 2 == 0 }.map { it * 2 }.toListprintln(result) // 输出: [4, 8]单线程场景:优先使用迭代器的remove方法,避免直接修改集合。多线程场景:读多写少:使用CopyOnWriteArrayList。写操作频繁:显式同步(synchronized/ReentrantLock)。Kotlin 特性:结合协程、Sequence等特性,减少锁竞争和临时集合开销。
ConcurrentModificationException的本质是集合结构一致性的维护机制。通过理解modCount与expectedModCount的协作逻辑,结合Kotlin的迭代器、并发集合和协程特性,开发者可以轻松规避该异常,写出更健壮的代码。
来源:瑗瑗课堂
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!