volatile 关键字对语言和实现它的平台具有很大的主观性。虽然 Java 在所有架构中都提供了一致的 volatile 行为,但对于直接编译到本机机器平台的语言(例如 C/C++),情况并非如此。让我们试着理解为什么会这样。
设 a, b 是一组程序动作 P 的成员,并且 v_{n} (a) 是将波动性要求应用于动作的函数,其中下标 _n 表示第 _n 次迭代,其中一个 volatile action 被应用,并且 \rightarrow 是前面的操作符,这在前面已经解释过了。对于所有程序操作,以下规则都成立:
v_n(a) \rightarrow v_{n+1}(a)
a \rightarrow v_n(b) \Rightarrow a \rightarrow v_{n+i}(b) 其中 i \in \mathbb{N}
规则 1 说所有 volatile 函数都强制执行总顺序,其中函数 v_{n} (a) 总是在 v_{n+1} (a) 之前,其中规则 2 说如果动作 a 在 volatile 函数之前在第 _n 次迭代的动作 b 上,动作 a 必须先于应用于 b 的所有后续 volatile 函数。
这在 Java 中是一个非常强的内存要求,实际上它比 C/C++ 强得多。 C/C++ 语言规范对内存排序没有这种限制,而是由编译器实现来决定如何围绕易失性操作对非易失性操作进行排序。
让我们通过一个简单的代码示例来考虑这些规则如何影响程序执行:
int a = 0;
int b = 0;
volatile int count = 0;
a = 1;
count = 1;
b = 2;
count = 2;
在 C/C++ 中,volatile 关键字仅保证 count 变量不能相互重新排序,即。如果 count == 2,则 count = 1 必须在它之前。但是,既不能保证 a == 1,也不能保证 b == 2。
在 Java 中,给定上面定义的更强保证,那么如果 count == 1,那么断言 a == 1 必须为真。同样,如果 count == 2 则断言 a == 1 && b == 2 必须为真。这就是 Java 提供 C/C++ 不提供的严格内存保证的含义。
但这并不意味着 C/C++ 的行为方式与 Java 不同。是否这样做取决于(1)编译器是否执行任何可能以令人惊讶的顺序但合法顺序的代码重新排序,以及(2)底层机器架构是否支持相同的严格内存顺序,前提是编译器不会执行任何令人惊讶的代码重新排序。
例如,在 gcc 上编译代码并在所有 x86 平台上设置 -O0 将符合(并且比)Java 的内存模型更严格,但其他架构(如 PowerPC、Alpha、Itanium)都支持较弱的内存模型,可以表现出程序员可能没有预料到的令人惊讶的程序行为。警告讲师!
无论如何,如果您对更多内存模型一致性规则感兴趣,您可能想观看英特尔对 x86 内存模型的解释,其中详细解释了内存排序的细微差别。尽情享受吧!