1.可见性
JMM内存模型,对volatile操作后本地缓存失效,线程都去主存读最新的值。涉及到MESI缓存一致性模型。
详细的后面再补。
2.禁止指令重排
- JMM 中的顺序性保障:
- JMM 保证了对
volatile
变量的读写操作具有顺序性。具体来说,它确保了在volatile
变量的写操作之前的所有操作不能被重排序到写操作之后,反之亦然。 - 这种顺序性保障主要针对的是编译器和 JMM 的优化,例如,在编译器优化时,JMM 会防止重排序带来的问题。
- JMM 保证了对
- CPU 层面的重排序:
- 即使 JMM 确保了在编译器和 JVM 层面的顺序性,实际的 CPU 可能仍然进行重排序,以优化指令的执行顺序。
- 这种重排序可能会导致在多核处理器环境中,某些操作的顺序被改变,从而影响程序的正确性。
- 内存屏障的作用:
- 内存屏障(Memory Barriers)是硬件层面上的机制,用于确保 CPU 的执行顺序符合程序的预期。
- 内存屏障的插入会阻止 CPU 在特定的操作之间进行重排序。例如,StoreStore Barrier 可以阻止写操作的重排序,StoreLoad Barrier 可以阻止写操作和读操作之间的重排序。
- 这些屏障可以确保不同 CPU 核心之间的缓存一致性,并确保所有线程看到的内存状态是一致的。
- 结合起来:
volatile
关键字提供了对变量的基本顺序性保障,但为了确保在硬件层面的正确性,JMM 需要通过内存屏障来实现。- 在实际的实现中,JMM 会在对
volatile
变量的读写操作前后插入适当的内存屏障,以确保 CPU 的行为与 JMM 规范相符。
总的来说,volatile
提供了编译器和 JMM 层面的顺序性保障,但内存屏障确保了硬件层面的顺序性和可见性。两者结合在一起,确保了多线程环境下的正确性。
内存屏障有四个:
- LoadLoad(读读屏障):先执行屏障前的 读,后执行屏障后的 读。
- LoadStore(读写屏障):先执行屏障前的 读,后执行屏障后的 写。
- StoreLoad(写读屏障):先执行屏障前的 写,后执行屏障后的 读。
- StoreStore(写写屏障):先执行屏障前的 写,后执行屏障后的 写。
疑惑点:
1.到底是JMM还是内存屏障保证了指令不乱序?
都是,内存屏障本质在不同操作系统上是不同的指令,JMM为了屏蔽不同操作系统的差异,自己定义了以上四个命令,实际用的还是操作系统提供的命令。
2.内存屏障指令在代码上位置?
加的位置:严格意义上,如果完全确保volatile的顺序:
比如wirte命令,假设在该命令之前有读取,我们需要确保读取在wirte之前执行,所以加LoadStore;
假设在该命令之前有写入,我们需要确保读取在wirte之前执行,所以加StoreStore;
假设在该命令之后有读取,我们需要确保读取在wirte之后执行,所以加StoreLoad;
假设在该命令之后有写入,我们需要确保写入在wirte之后执行,所以加StoreStore;
所以位置如下:
1 | LoadStore,StoreStore|write|StoreLoad,StoreStore |
如果是read命令:
1 | // LoadLoad 屏障:确保后续读取不会重排序到 volatile 读取之前 |
- 本文作者: 宏
- 本文链接: http://sasuke.top/2024/09/05/volatile/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!