易失性和原子性是两个不同的概念。 Volatile 确保某个预期的(内存)状态在不同线程中为 true,而 Atomics 确保对变量的操作以原子方式执行。
以Java中的两个线程为例:
线程 A:
value = 1;
done = true;
线程 B:
if (done)
System.out.println(value);
从value = 0 和done = false 开始,线程规则告诉我们,线程B 是否打印值是未定义的。 此外,value 在那一点上也是未定义的! 要解释这一点,您需要了解一点 Java 内存管理(可能很复杂),简而言之:线程可能会创建变量的本地副本,并且 JVM 可以重新排序代码以优化它,因此不能保证上述代码完全按照该顺序运行。将 done 设置为 true 并且 then 将值设置为 1 可能是 JIT 优化的结果。
volatile 仅确保在访问此类变量时,新值将立即对所有其他线程可见并且执行顺序确保代码位于您期望的状态。所以在上面代码的情况下,将done定义为volatile将确保每当线程B检查变量时,它要么是假的,要么是真的,如果是真的,那么value有也被设置为 1。
作为 volatile 的副作用,这种变量的值是在线程范围内以原子方式设置的(以非常小的执行速度成本)。然而,这仅在 32 位系统上很重要,即使用长(64 位)变量(或类似变量),在大多数其他情况下,设置/读取变量无论如何都是原子的。但是原子访问和原子操作之间有一个重要的区别。 Volatile 只保证访问是原子的,而 Atomics 保证操作是原子的。
举个例子:
i = i + 1;
无论你如何定义 i,在执行上述行时读取值的不同线程可能会得到 i 或 i + 1,因为 操作 不是原子的。如果另一个线程将 i 设置为不同的值,在最坏的情况下,线程 A 可以将 i 设置回它之前的任何值,因为它只是在根据旧值计算 i + 1 的中间,然后设置 i再次回到那个旧值 + 1。解释:
Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1
像 AtomicInteger 这样的原子确保此类操作以原子方式发生。所以上述问题不会发生,一旦两个线程都完成,我将是 1000 或 1001。