Volatile 可防止 3 种不同类型的问题:
我假设是X86..
首先,X86 上的缓存始终是一致的。因此,不会发生这样的情况:一个 CPU 将某个变量的存储提交到缓存后,另一个 CPU 仍会加载该变量的旧值。这是 MESI 协议的领域。
假设 Java 字节码中的每个 put 和 get 都被转换(并且没有优化)到 CPU 上的存储和加载,那么即使没有 易失性,每个 get 也会看到对同一地址的最新 put。
这里的问题是编译器(在本例中为 JIT)有很大的自由度来优化代码。例如,如果它检测到在循环中读取相同的字段,它可能会决定将该变量提升到循环之外,如下所示。
for(...){
int tmp = a;
println(tmp);
}
Run Code Online (Sandbox Code Playgroud)
吊装后:
int tmp = a;
for(...){
println(tmp);
}
Run Code Online (Sandbox Code Playgroud)
如果该字段仅被 1 个线程触及,那么这很好。但如果该字段被另一个线程更新,第一个线程将永远不会看到更改。使用 volatile 可以防止此类可见性问题,这实际上是以下行为:
那么 volatile 还有一个非常重要的方面;易失性可防止某些 CPU 执行的指令流中不同地址的加载和存储被重新排序。JIT 编译器和 CPU 有很大的自由度来重新排序加载和存储。尽管在 X86 上,由于存储缓冲区的原因,只能将较旧的存储与较新的加载重新排序到不同的地址。
想象一下下面的代码:
int a;
volatile int b;
thread1:
a=1;
b=1;
thread2:
if(b==1) print(a);
Run Code Online (Sandbox Code Playgroud)
易失性的事实b阻止了 storea=1跳转到 store 之后b=1。并且它还可以防止 的负载a在 的负载之前跳入b。这样线程 2 就可以保证a=1在读取时看到b=1。
所以使用 volatile,可以保证非 volatile 字段对其他线程可见。
如果您想了解 易失性 是如何工作的,我建议深入研究 Java 内存模型,该模型以同步和发生之前规则表示,正如 Margeret Bloom 已经指出的那样。我已经给出了一些低级细节,但对于 Java,最好使用这个高级模型,而不是从硬件角度考虑。只考虑硬件/围栏只适合专家,不可移植且非常脆弱。
| 归档时间: |
|
| 查看次数: |
1669 次 |
| 最近记录: |