如何安全地在多线程环境下读取与替换只读列表

作者:袖梨 2026-07-09

本文介绍一种比 synchronized 更轻量、更高效的线程安全方案:使用 volatile 或 AtomicReference 替代同步方法,配合不可变列表(如 List.copyOf())实现无锁读取与原子更新。

本文介绍一种比 `synchronized` 更轻量、更高效的线程安全方案:使用 `volatile` 或 `atomicreference` 替代同步方法,配合不可变列表(如 `list.copyof()`)实现无锁读取与原子更新。

在高并发场景下,频繁调用 synchronized 方法会带来显著的性能开销和可伸缩性瓶颈。你当前的实现中,getFoo() 和 refresh() 均加了 synchronized,但实际需求仅是:读操作不修改状态,写操作仅整体替换列表——这完全符合“不可变发布”(immutable publication)模型,无需重量级锁。

✅ 推荐方案:volatile + 不可变列表

由于 list 仅被整体赋值(无原地修改),且所有读取均发生在赋值之后,volatile 字段即可提供所需的 happens-before 保证:新列表的构造与发布对所有线程可见。

private volatile List<Foo> list = List.of(); // 初始化为空不可变列表public Foo getFoo(Condition condition) {    // 无需同步:volatile 保证读到最新引用,且列表本身不可变    for (Foo foo : list) {        if (condition.check(foo)) {            return foo;        }    }    return null;}public void refresh() {    // fetchFromDB() 返回新列表;用 List.copyOf() 强制不可变    List<Foo> newList = List.copyOf(fetchFromDB());    list = newList; // volatile 写入,对所有线程立即可见}

? List.copyOf()(Java 10+)不仅创建不可变副本,还会在运行时检查源列表是否被意外修改(抛出 UnsupportedOperationException),是比 Collections.unmodifiableList() 更安全的选择。

? 备选方案:AtomicReference<List<Foo>>

若需更复杂的原子操作(如 CAS 更新、条件刷新),可选用 AtomicReference:

private final AtomicReference<List<Foo>> listRef = new AtomicReference<>(List.of());public Foo getFoo(Condition condition) {    for (Foo foo : listRef.get()) { // get() 具有 volatile 读语义        if (condition.check(foo)) {            return foo;        }    }    return null;}public void refresh() {    List<Foo> newList = List.copyOf(fetchFromDB());    listRef.set(newList); // set() 具有 volatile 写语义}

⚠️ 关键注意事项

  • 不可变性是前提:list 引用指向的对象必须不可被修改。若 fetchFromDB() 返回可变 ArrayList,务必通过 List.copyOf() 或 Collections.unmodifiableList() 封装,否则其他线程可能在遍历时遭遇 ConcurrentModificationException 或数据不一致。
  • 元素也需线程安全:Foo 实例自身不应存在共享可变状态;若含内部状态,应确保其线程安全(如 final 字段、不可变设计或同步访问)。
  • 避免隐式修改:切勿将 list 赋值给非 final 可变变量后进行修改(如 List<Foo> temp = list; temp.add(...)),这会破坏线程安全性。
  • 文档即契约:在字段声明旁添加明确注释:
    // Thread-safe: list is volatile and always points to an unmodifiable List.// Never mutate the list or its elements after assignment.private volatile List<Foo> list = List.of();

✅ 总结

相比 synchronized,volatile 方案消除了锁竞争,提升读吞吐量;结合 List.copyOf() 确保不可变语义,从根源杜绝并发修改风险。该模式简洁、高效、易测试,是“只读+全量更新”场景下的最佳实践。

相关文章

精彩推荐