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

未使用循环仍触发java.util.ConcurrentModificationException问题排查

核心错误排查

问题本质

ConcurrentModificationException不是只有手动写循环才会触发——removeIf方法内部依赖迭代器遍历集合并执行删除操作。而你用mutableMapOf()默认生成的是LinkedHashMap,它的迭代器是**快速失败(fail-fast)**类型:如果迭代过程中集合被其他线程修改(迭代器自身的remove方法除外),就会抛出这个异常。

你的场景里,registerSession可能在业务线程执行添加操作,而SseEmitter的onTimeout/onError回调大概率在另一个线程触发。这就导致多线程并发修改同一个MutableSet:当removeIf正通过迭代器遍历集合时,其他线程执行found.add(sessionData)修改了集合结构,直接触发快速失败机制抛出异常。

关键验证点

  • 确认registerSession的调用线程和removeSessionForUser的执行线程是否为同一个
  • 默认mutableSetOf()返回的是LinkedHashSet,同样是fail-fast实现,不支持并发修改

解决方案

方案1:替换为线程安全集合

把普通集合换成线程安全的实现,从根源避免并发冲突:

// 使用ConcurrentHashMap保证Map层面线程安全
val userIdToSessionDataMap: MutableMap<String, MutableSet<UserSessionData>> = ConcurrentHashMap()

fun registerSession(userId: String, jti: String): SseEmitter {
    val sseEmitter = SseEmitter(timeout)
    sseEmitter.onTimeout { removeSessionForUser(userId, jti) }
    sseEmitter.onError { removeSessionForUser(userId, jti) }

    val sessionData = UserSessionData(jti, sseEmitter)
    // 用CopyOnWriteArraySet替代普通Set,它通过复制快照实现线程安全
    userIdToSessionDataMap.computeIfAbsent(userId) { CopyOnWriteArraySet() }.add(sessionData)
    return sseEmitter
}

CopyOnWriteArraySet的迭代器遍历的是集合快照,不会因为其他线程修改集合而抛出异常。

方案2:手动加锁保护

如果不想替换集合,就在所有操作集合的逻辑外层加锁,保证同一时间只有一个线程能操作集合:

private val lock = Any()

private fun removeSessionForUser(userId: String, jti: String) {
    synchronized(lock) {
        val found = userIdToSessionDataMap[userId]
        found?.removeIf { it.jti == jti }
    }
}

fun registerSession(userId: String, jti: String): SseEmitter {
    val sseEmitter = SseEmitter(timeout)
    sseEmitter.onTimeout { 
        synchronized(lock) {
            removeSessionForUser(userId, jti) 
        }
    }
    sseEmitter.onError { 
        synchronized(lock) {
            removeSessionForUser(userId, jti) 
        }
    }

    val sessionData = UserSessionData(jti, sseEmitter)
    synchronized(lock) {
        val found = userIdToSessionDataMap[userId]
        if (found != null) {
            found.add(sessionData)
        } else {
            userIdToSessionDataMap[userId] = mutableSetOf(sessionData)
        }
    }
    return sseEmitter
}

通过synchronized锁保证集合操作的原子性,避免并发修改冲突。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:12:13