如何实现线程安全的只读列表读取与原子刷新

夜雪姑娘_7882

夜雪姑娘_7882

2026-07-08

713人浏览

原创

如何实现线程安全的只读列表读取与原子刷新

本文介绍在列表仅通过全量替换更新(无中间修改)场景下,用 volatile 或 atomicreference 替代 synchronized 方法实现高效、安全的线程间可见性保障,并强调不可变性约束与防御性封装的重要性。

本文介绍在列表仅通过全量替换更新(无中间修改)场景下,用 volatile 或 atomicreference 替代 synchronized 方法实现高效、安全的线程间可见性保障,并强调不可变性约束与防御性封装的重要性。

在高并发读多写少的场景中(例如缓存配置列表、规则集或元数据),频繁使用 synchronized 方法会导致读操作串行化,成为性能瓶颈。你当前的实现虽正确,但 synchronized 锁住整个 getFoo() 和 refresh() 方法,不仅限制了并发读能力,还隐含了不必要的锁粒度——因为 list 本身从不被原地修改,仅被整体替换。

更优解是利用 Java 内存模型(JMM)提供的可见性保证,而非互斥锁。核心思路是:确保新列表的发布对所有线程立即可见,且该列表及其元素不可变。以下是两种推荐方案:

Supermeme
Supermeme

Supermeme是一款通过文字生成梗图并搜索模板的 AI Meme 创作工具。

下载

✅ 方案一:volatile List(简洁直接)

private volatile List<foo> list = Collections.emptyList(); // 初始值也需安全

public Foo getFoo(Condition condition) {
    // 无锁读取,JVM 保证看到最新发布的 list 引用
    for (Foo foo : list) {
        if (condition.check(foo)) {
            return foo;
        }
    }
    return null;
}

public void refresh() {
    // 创建不可变副本并原子发布
    List<foo> newList = List.copyOf(fetchFromDB()); // Java 10+,线程安全快照
    list = newList; // volatile 写入,触发内存屏障,保证可见性
}</foo></foo>

✅ 方案二:AtomicReference>(更显式、可扩展)

private final AtomicReference<list>> listRef = 
    new AtomicReference(Collections.emptyList());

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 写语义
}</foo></list>

⚠️ 关键前提与注意事项

  • 不可变性是基石:fetchFromDB() 返回的原始列表必须被封装为不可变视图(如 List.copyOf() 或 Collections.unmodifiableList())。若后续代码意外修改了该列表(如调用 add()),将破坏线程安全性,且难以排查。
  • 元素自身也需线程安全:Foo 实例若含可变状态,应设计为不可变对象(final 字段 + 无 setter),否则即使列表引用安全,其内部状态仍可能引发竞态。
  • 避免“假共享”陷阱:不要在 refresh() 中直接赋值 list = fetchFromDB() —— 若返回的是可变 ArrayList,其他线程可能在遍历时遭遇 ConcurrentModificationException 或读到脏数据。
  • 初始化与空值防护:初始值建议设为 Collections.emptyList() 或 List.of(),避免 null 检查开销;getFoo() 中无需额外判空,因 List.copyOf(null) 会抛 NullPointerException,应在 fetchFromDB() 层面保证非空或做预处理。

✅ 总结

相比 synchronized,volatile 或 AtomicReference 消除了锁竞争,使读操作完全无阻塞,显著提升吞吐量。其本质是用“安全发布”替代“临界区保护”。只要严格遵循“发布即不可变”的契约,这种模式既简洁又健壮。务必在代码注释中明确标注:“⚠️ 此列表必须不可变,任何原地修改将导致线程安全失效”,并辅以静态检查(如 SpotBugs 的 IS2_INCONSISTENT_SYNC 规则)或单元测试(模拟并发读写)加强保障。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

9757

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

6902

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6092

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1064

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

888

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1276

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2549

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19891

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1155

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Lua 5.3 中文开发手册
Lua 5.3 中文开发手册

共0课时 | 0人学习