跳转至

Java 并发核心机制总结:原子性、CAS、内存屏障、缓存一致性协议(MESI)

本笔记围绕以下几个关键词展开:原子性(Atomicity)CAS(Compare-And-Swap)内存屏障(Memory Barrier)缓存一致性协议(MESI)。通过逐步深入的逻辑与具体示例,我们将从根源解释这些机制是如何协同工作以确保并发安全的。


一、原子性是什么?

原子性指操作是不可中断的最小执行单元,要么全部执行完成,要么完全不执行。在并发环境中,原子性可防止“线程间中间状态可见”。

示例:非原子操作的危险

i++; // 实际上包括:读取 i → 自增 → 写回 i

在并发中,多个线程可能读取同一值,导致结果不一致。


二、CAS:实现原子性的硬件机制

CAS(Compare-And-Swap)是一种乐观锁机制,基于硬件指令实现原子更新。

if (memory == expected) {
    memory = newValue;
    return true;
} else {
    return false;
}

底层实现

  • x86 架构:使用 lock cmpxchg 实现。
  • lock 前缀在多核中用于通知其他核心暂停对该缓存行的访问(通过总线锁或缓存一致性协议)。

三、现代 CPU 是如何实现 CAS 原子性的?

  • 锁住总线(旧方法,昂贵,影响性能)
  • 缓存一致性协议(MESI):现代 CPU 优选方案,精细控制缓存同步。

MESI 协议四个状态

状态 含义
M(Modified) 当前核心独占且已修改
E(Exclusive) 当前核心独占但未修改
S(Shared) 多核心共享,主存一致
I(Invalid) 无效缓存,需重新拉取

MESI 如何保障 CAS 原子性?

  • CAS 操作前获取该地址缓存行的 ExclusiveModified 权限。
  • 其他核心如需访问该地址,需先通过协议协商,不能直接从 L3 或主存读取。

四、CAS 本身是否足够?

虽然 CAS 能保证原子性,但无法防止前后的指令被 CPU 或编译器重排,导致并发错误。

示例:发布-消费模型中的重排序问题

obj.value = 42;
cas(obj.flag, 0, 1); // 发布完成信号

如果发生重排序:

cas(obj.flag, 0, 1);
obj.value = 42;

另一个线程看到 flag == 1 后读取 value,可能读到旧值。


五、内存屏障的作用:保证可见性与有序性

内存屏障(Memory Barrier)用于防止重排序、控制可见性。

屏障类型 含义
LoadLoad 防止前后读乱序
StoreStore 防止前后写乱序
LoadStore 防止读后写提前
StoreLoad 强屏障,常用于 volatile

CAS 搭配的内存屏障

  • CAS 前:插入 StoreStore 屏障,确保之前写的内容(如 obj.value)不会被重排到 CAS 之后。
  • CAS 后:插入 LoadLoad + LoadStore 屏障,保证读写操作不会被提前到 CAS 之前。

六、总结:并发安全三要素协同工作

特性 实现机制 示例
原子性 CAS(lock cmpxchg) AtomicInteger.incrementAndGet()
可见性 内存屏障 / volatile volatile 保证刷新主内存
有序性 内存屏障防止指令重排 obj.value -> CAS(obj.flag) 顺序保证

七、关键反驳与实验证明

有人认为:

“即使顺序是 CAS -> value 写也没事,线程看到 flag==1 后 value 应该也有了。”

我们用“发布-消费”模型反驳该观点:

Thread A:
obj.value = 42;
cas(obj.flag, 0, 1); // 正确顺序,value 先初始化

Thread B:
if (obj.flag == 1) {
    read(obj.value); // 安全
}

如果重排序成:

Thread A:
cas(obj.flag, 0, 1);
obj.value = 42;

Thread B:
if (obj.flag == 1) {
    read(obj.value); // 可能读到旧值,错误!
}

结论:原子性 ≠ 并发安全。完整并发语义必须靠内存屏障保障顺序性与可见性。


八、延伸思考

  • MESI 之外还有 MOESI、MESIF 等协议,进一步优化缓存共享。
  • Java 9 之后的 VarHandle 提供更底层可控的内存语义。
  • 高性能队列(如 Disruptor)广泛应用 CAS + 内存屏障技术。

九、深入扩展:Redis、Lock 前缀、总线锁与缓存一致性协议

本节我们从 Redis 的单线程模型引出硬件级并发控制机制,深入理解 lock 指令、总线锁、MESI 缓存一致性协议三者的关系与区别。


1. Redis 的并发模型与原子性

  • Redis 是单线程处理命令(事件循环 + IO 多路复用)
  • 命令如 DECR key 是逻辑原子的,但不是 CPU 层面的单指令
  • 实际操作:LOAD → DEC → STORE,但 Redis 串行执行,天然顺序和线程安全

2. DECR 在硬件上的类比

在 CPU 层,相当于:

LOAD R1, [key]   ; 加载
DEC R1           ; 自减
STORE [key], R1  ; 写回
  • 并非原子,需要额外机制来保证“整体不可中断”

3. lock 前缀:请求原子性的标志

lock cmpxchg [mem], eax
  • lock 是 x86 架构中告诉 CPU:我需要对这条内存操作执行原子性保障
  • 不能被中断、不能和其他核心交错执行

4. 总线锁(Bus Lock):早期机制

  • 早期 CPU 使用 锁定地址总线 的方式来阻止其他核心访问共享内存
  • 优点:实现简单
  • 缺点:
  • 粒度粗,整个系统总线暂停
  • 影响大,不可扩展

5. 缓存一致性协议(MESI):现代机制

  • lock 操作针对的是可缓存地址时(RAM),现代 CPU 使用 MESI 协议
  • 将目标地址所在缓存行标记为 M(Modified)
  • 通知其他核心将该行置为 I(Invalid)
  • 达到锁定缓存行、保持原子性的效果

6. 总线锁 vs 缓存一致性协议:区别对比

项目 总线锁(Bus Lock) MESI 缓存一致性协议
锁定范围 所有内存访问总线 仅相关缓存行
粒度 粗,系统级 精细,按缓存行
并发效率
是否阻塞无关操作
现代架构使用 主流

7. MESI 协议 + lock 的执行流程

Core A            Core B
------            ------
L1: [X:M]         L1: [X:I]  ← B 无效
执行 lock cmpxchg
→ A 修改缓存行
→ 操作结束前,其他核心不能访问 X
→ 完成后同步可见性
  • 利用缓存协议保持“看似一瞬间完成”的原子性
  • 无需锁总线

8. 特殊情况:仍然使用总线锁的场景

场景 是否使用 Bus Lock
操作 MMIO(设备内存)
不可缓存内存
普通变量 / 数组元素 否,使用 MESI

9. 总结

lock 是语义标志,具体如何实现原子性取决于底层硬件:

  • 对普通内存:使用 MESI 协议锁缓存行
  • 对 IO 地址:使用总线锁

Redis 不使用 lock 或硬件原子性,而是靠事件循环串行执行实现“逻辑原子性”。

回到页面顶部