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
相关产品推荐
相关产品推荐

