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

Kotlin多线程访问集合求助:并发修改异常问题

Kotlin多线程访问集合的正确姿势

嘿,我来帮你搞定这个问题~你遇到的ConcurrentModificationException,核心原因其实是对@Synchronized的理解有点偏差:给myList的getter加注解,只是保证获取集合实例这个动作是线程安全的,但当你拿到集合之后,forEach遍历和remove修改这两个操作是在各自线程里独立执行的,完全没有同步约束——这就导致遍历到一半集合被修改,直接触发了异常。

下面给你三种靠谱的解决方式,你可以根据自己的场景选择:

1. 使用线程安全的集合类(读多写少场景首选)

Java并发包提供的CopyOnWriteArrayList天生就是线程安全的,遍历的时候会基于集合的快照进行,完全不会因为并发修改抛出异常。适合读操作远多于写操作的场景(比如你的示例里遍历是主要操作,删除是偶尔行为):

import java.util.concurrent.CopyOnWriteArrayList

class ListTest() {
    val myList = CopyOnWriteArrayList<String>().apply {
        repeat(10000) { add("stuff: $it") }
    }
}

fun main() = runBlocking<Unit> {
    val listTest = ListTest()
    launch(Dispatchers.Default) {
        delay(1L)
        listTest.myList.remove("stuff: 54")
    }
    launch {
        listTest.myList.forEach { println(it) }
    }
}

注意:这个类每次修改都会复制底层数组,所以写操作的开销比较大,读多写少的时候用它最划算。

2. 手动同步所有集合操作(通用场景)

把对集合的所有操作(遍历、修改)都封装成同步方法,用同一个锁对象保证同一时间只有一个线程能操作集合:

class ListTest() {
    private val myList = mutableListOf<String>()

    init {
        repeat(10000) { myList.add("stuff: $it") }
    }

    // 同步遍历操作
    fun iterateList(action: (String) -> Unit) {
        synchronized(this) {
            myList.forEach(action)
        }
    }

    // 同步删除操作
    fun removeElement(element: String) {
        synchronized(this) {
            myList.remove(element)
        }
    }
}

fun main() = runBlocking<Unit> {
    val listTest = ListTest()
    launch(Dispatchers.Default) {
        delay(1L)
        listTest.removeElement("stuff: 54")
    }
    launch {
        listTest.iterateList { println(it) }
    }
}

这里用this作为锁对象,也可以专门创建一个private val lock = Any()来做锁,避免和其他可能的同步操作冲突。

3. 用协程Mutex(Kotlin协程场景首选)

既然你用了runBlocking和协程,那用Kotlin协程专用的Mutex会更合适——它不会阻塞线程,只会挂起协程,性能更优:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class ListTest() {
    private val myList = mutableListOf<String>()
    private val mutex = Mutex() // 协程互斥锁

    init {
        repeat(10000) { myList.add("stuff: $it") }
    }

    suspend fun iterateList(action: (String) -> Unit) {
        mutex.withLock {
            myList.forEach(action)
        }
    }

    suspend fun removeElement(element: String) {
        mutex.withLock {
            myList.remove(element)
        }
    }
}

fun main() = runBlocking<Unit> {
    val listTest = ListTest()
    launch(Dispatchers.Default) {
        delay(1L)
        listTest.removeElement("stuff: 54")
    }
    launch {
        listTest.iterateList { println(it) }
    }
}

withLock会自动帮你加锁和释放锁,不用手动处理,非常安全。

总结一下:不要只同步集合的getter,要同步整个操作过程;协程环境优先用Mutex,普通线程环境用synchronized块,读多写少用CopyOnWriteArrayList。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 14:22:40