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

Kotlin未修改列表却触发ConcurrentModificationException问题排查

解决RecyclerView遍历列表时的ConcurrentModificationException问题

你遇到的ConcurrentModificationException是因为遍历列表过程中,列表被其他线程执行了结构性修改(即便你没有主动修改,也可能是列表的引用替换、内容更新和遍历操作并发导致)。结合你的场景,问题核心在于:Realm结果变化时后台线程更新displayableMessages的操作,与主线程RecyclerView遍历列表的操作没有同步,导致迭代器检测到列表结构异常变化,抛出异常。

解决方案

1. 使用不可变列表(推荐)

将后台线程生成的列表转为不可变列表后再赋值给displayableMessages,从根源上避免并发修改的可能:

// 后台线程处理Realm数据
GlobalScope.launch(Dispatchers.IO) {
    val copiedMessages = Realm.getDefaultInstance().use { realm ->
        realm.copyFromRealm(realmResults)
    }
    // 转为不可变列表
    val immutableList = copiedMessages.toList()
    // 主线程更新引用并通知RecyclerView
    launch(Dispatchers.Main) {
        displayableMessages = immutableList
        notifyDataSetChanged()
    }
}

// onBindViewHolder中直接遍历不可变列表
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val message = ... // 获取当前绑定的消息对象
    val displayableMessage = displayableMessages?.find { item -> item?.messageId == message.id }
    // 后续视图绑定逻辑
}

2. 加锁同步列表访问

如果必须使用可变列表,用锁包裹列表的赋值和遍历操作,确保两个操作不会并发执行:

private val listLock = Any()
private var displayableMessages: MutableList<DisplayableMessage>? = null

// 后台线程更新列表
GlobalScope.launch(Dispatchers.IO) {
    val copiedMessages = Realm.getDefaultInstance().use { realm ->
        realm.copyFromRealm(realmResults)
    }
    val newList = copiedMessages.toMutableList()
    // 同步赋值操作
    synchronized(listLock) {
        displayableMessages = newList
    }
    // 主线程通知RecyclerView更新
    launch(Dispatchers.Main) {
        notifyDataSetChanged()
    }
}

// onBindViewHolder中同步遍历列表
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val message = ...
    var displayableMessage: DisplayableMessage? = null
    synchronized(listLock) {
        displayableMessage = displayableMessages?.find { item -> item?.messageId == message.id }
    }
    // 后续视图绑定逻辑
}

3. 使用线程安全的列表实现

使用CopyOnWriteArrayList(Java并发包提供的线程安全列表),它每次修改都会复制整个列表,遍历操作基于列表快照进行,不会触发并发修改异常:

import java.util.concurrent.CopyOnWriteArrayList

private var displayableMessages: CopyOnWriteArrayList<DisplayableMessage>? = CopyOnWriteArrayList()

// 后台线程更新列表
GlobalScope.launch(Dispatchers.IO) {
    val copiedMessages = Realm.getDefaultInstance().use { realm ->
        realm.copyFromRealm(realmResults)
    }
    val newList = CopyOnWriteArrayList(copiedMessages)
    launch(Dispatchers.Main) {
        displayableMessages = newList
        notifyDataSetChanged()
    }
}

// onBindViewHolder中直接遍历线程安全列表
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val message = ...
    val displayableMessage = displayableMessages?.find { item -> item?.messageId == message.id }
    // 后续视图绑定逻辑
}

额外优化建议

  • 处理Realm实例时,务必用use块自动关闭,避免资源泄漏:Realm.getDefaultInstance().use { ... }
  • 避免在onBindViewHolder中执行列表遍历操作(该方法会频繁调用,影响性能)。可以在后台线程将displayableMessages转为以messageId为Key的Map,在onBindViewHolder中直接通过Key查找:
// 后台线程转为Map
val messageMap = copiedMessages.associateBy { it.messageId }
// 主线程赋值
displayableMessageMap = messageMap
// onBindViewHolder中直接查找
val displayableMessage = displayableMessageMap?.get(message.id)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:33:24