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

Kotlin中不可变列表是否需要加锁?并发操作该列表需同步吗?

Kotlin 多线程下的不可变列表并发问题解答

基础结论

  • 没有任何同步约束的前提下,One()和Two()完全可以被不同线程同时调用、并行执行,不存在默认的串行执行限制。
  • 上述代码在多线程场景下存在明确的并发风险,是否需要加锁保护要结合业务需求判断,不能笼统认为“用了不可变列表就不需要任何同步”。

核心认知拆解

这里很容易踩中一个常见的认知误区:把两个完全不同的线程安全问题混为一谈。

  • listOf()返回的是不可变集合对象:这类对象没有任何修改内部元素、调整容量的公开方法,只要这个对象的引用对所有线程完整可见,所有线程对它的遍历、读取操作都是完全安全的,不会出现ConcurrentModificationException,也不会因为多线程并发读就出现内部状态错乱。
  • 但代码里用来持有列表的变量是var list——这是一个可变的引用,本身没有任何并发访问保障,这才是整个代码的风险来源。反过来如果list是val修饰的固定不可变引用,搭配不可变的集合对象,整个链路没有可变状态,天然就是线程安全的,不需要任何同步手段。

现有代码的具体并发风险

当两个函数被不同线程同时调度时,会出现两类典型问题:

  1. 可见性丢失:没有同步机制的前提下,Two()对list的重新赋值可能一直停留在执行线程的CPU工作内存,不会及时刷回主内存。执行One()的线程可能永远感知不到这个赋值变化,一直拿着最早的旧列表执行业务逻辑,和预期的语义完全不符。
  2. 指令重排风险:对象的创建流程(分配内存、执行构造方法、将引用赋值给变量)在没有同步约束时,顺序可能被JVM调整打乱。其他线程可能拿到一个已经完成赋值的引用,但它指向的是一个还没走完构造流程的半初始化列表,这时候执行遍历就可能触发空指针、数组越界等难以排查的异常。

到底要不要加锁?

完全不需要为了“不可变列表”这个标签盲目加锁,具体方案要匹配业务需求:

  • 如果业务要求One()从进入函数到遍历完成的全流程,必须使用同一份固定版本的列表、不中途被替换引用,那就需要加锁同时保护两个函数对list的读写操作。
  • 如果业务只要求每次读取list时能拿到最新的、完整初始化好的列表版本,遍历过程中就算引用被替换也不影响当前遍历的逻辑(因为不可变列表本身永远不会被修改),那完全不需要加重型锁,只要给可变的引用补上并发保障即可:
    • 给list加上@Volatile注解,保证赋值操作的可见性、禁止对象创建流程的指令重排;
    • 或者直接用AtomicReference持有列表引用,所有对引用的读、写操作都通过原子引用完成。
      这种场景下两个函数依然可以完全并行运行:执行One()的线程拿到引用后,遍历的是那个完整、不可变的列表对象;执行Two()的线程只是往原子引用里写入一个全新的不可变列表,两个操作不会对同一个内存对象产生修改,不存在竞态条件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 20:54:01