多线程安全问题:可见性、原子性、有序性
Java 全栈知识点问题汇总(上) | Java 全栈知识体系
CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
- CPU 增加了缓存,以均衡与内存的速度差异;// 导致可见性问题
- 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致原子性问题
- 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致有序性问题
问题:
1.CPU缓存行(Cache Line)和MESI协议 不是已经实现了可见性吗?为什么多线程还会有可见性问题?
-
MESI协议保证的是"最终一致性",而不是"实时一致性"。就是说保证最后一定是一致的,但是在进行同步到一致的过程中,另外一个线程来读可能会读到老的数据。
-
MESI协议是硬件层面的缓存一致性协议,它解决了"缓存副本一致性"的问题,但不能解决编译器优化、CPU重排序、刷新时机等导致的可见性问题。完整的可见性保证需要结合volatile、内存屏障、锁等机制。
-
这就是最终一致性 vs 强一致性的区别!
-
✅ MESI保证:最终一定会一致
-
❌ MESI不保证:同步过程中不会读到旧值
-
这就是为什么即使有MESI协议,多线程编程仍然需要考虑可见性问题,需要使用volatile、内存屏障、锁等机制来保证"立即可见"而不仅仅是"最终可见"。
-
-
三种导致"读到老值"的原因
| 原因 | 说明 | MESI能解决吗? |
|---|---|---|
| 缓存传播延迟 | 失效消息在总线上传播需要时间 | ❌ 不能 |
| 寄存器缓存 | 变量被优化到寄存器,不访问内存 | ❌ 不能 |
| 存储缓冲区 | 写入先在Store Buffer,未进入缓存 | ❌ 不能 |
2.上面说的原子性就是,多线程的出现破坏了原子性?
- 不是"多线程破坏了原子性",而是:
- 某些操作在单线程环境下天然具有原子性,但在多线程环境下失去了原子性保证。
- 多线程没有"破坏"原子性,而是暴露了操作本身的非原子性问题。我们需要通过同步机制来保证原子性。
| 概念 | 说明 |
|---|---|
| 操作本身的性质 | count = count + 1 是非原子操作(多步骤) |
| 单线程环境 | 没有竞争,非原子操作也能正确执行 |
| 多线程环境 | 出现竞争,非原子操作导致数据不一致 |
| 原子性的保证 | 通过锁或原子指令,让操作在多线程下也能正确执行 |
浙公网安备 33010602011771号