跳到正文
Elaine Blog
返回

JMM、Happens-Before 与三类并发性质

并发编程

并发正确性不能用“多跑几次没出错”证明。你需要为共享状态建立明确的 happens-before 关系,并分别检查原子性、可见性 和有序性。本文给出一张可以用于代码审查的推理表。

Table of contents

Open Table of contents

三类性质解决不同问题

性质问题典型错误
原子性操作会不会被其他线程看见中间状态count++ 丢失更新
可见性一个线程何时看到另一个线程的写入循环读不到停止标志
有序性编译器、JIT、CPU 能否重排操作对象引用先于初始化结果被观察

volatile 能建立可见性和禁止相关重排,但不能把复合操作自动变成原子操作。

final class 错误计数器 {
    private volatile int count;
    void increment() { count++; }
}

count++ 仍包含读取、加一和写回。两个线程可能读取同一个旧值并覆盖结果。应使用 AtomicInteger.incrementAndGet()、锁, 或让状态只由单一线程拥有。

Happens-Before 是可见性承诺

若操作 A happens-before B,JMM 保证 B 能看到 A 的效果。常用规则包括:

Happens-before 不是墙钟时间。两个操作即使现实中先后发生,如果没有同步关系,程序也不能依赖这种观察。

安全发布不可变对象

下面做法通过 volatile 引用发布一个完全构造的配置:

final class 配置中心 {
    private volatile 配置 current = new 配置("默认", 3);

    配置 get() { return current; }
    void reload(配置 next) { current = next; }
}

record 配置(String name, int retries) {}

写入 current 之前的构造操作对随后读取该 volatile 引用的线程可见。Record 字段不可重新赋值,但字段指向的集合仍需 防御性复制。

数据竞争与竞态条件

**数据竞争(Data Race)**指两个线程无 happens-before 地访问同一变量,且至少一个写入。**竞态条件(Race Condition)**更宽泛:结果依赖不可控时序。使用原子变量可以消除单变量数据竞争,但“先检查库存再扣减”的跨变量规则 仍可能产生竞态,需要锁、事务或状态机保证整体不变量。

代码审查清单

  1. 找出所有可变共享状态和它的所有者。
  2. 标出每个读写之间的同步边。
  3. 检查复合不变量是否在同一原子边界内。
  4. 检查取消、超时和异常路径是否仍释放同步资源。
  5. 使用并发测试工具放大时序,不把单次通过当作证明。

下一步

阅读synchronized 的实现与边界,把 JMM 规则落到监视器 互斥和可见性上。


分享这篇文章:

上一篇
线程生命周期、等待通知与中断协议
下一篇
synchronized 的实现、锁状态与适用边界