易失性和原子性是两个不同的概念。Volatile 确保某个预期的(内存)状态在不同线程中为 true,而 Atomics 确保对变量的操作以原子方式执行。
以 Java 中的两个线程为例:
线程 A:
value = 1;
done = true;
线程 B:
if (done)
System.out.println(value);
线程规则告诉我们value = 0
,done = false
线程 B 是否打印值是未定义的。此外,此时值也未定义!要解释这一点,您需要了解一点Java内存管理(可能很复杂),简而言之:线程可能会创建变量的本地副本,并且JVM可以重新排序代码以优化它,因此不能保证上面的代码完全按照这个顺序运行。将 done 设置为 true然后将 value 设置为 1 可能是 JIT 优化的可能结果。
volatile
仅确保在访问此类变量时,新值将立即对所有其他线程可见,并且执行顺序确保代码处于您期望的状态。所以在上面的代码中,定义done
为volatile将确保每当线程 B 检查变量时,它要么为假,要么为真,如果为真,则value
也被设置为 1。
作为volatile的副作用,这种变量的值是在线程范围内以原子方式设置的(以非常小的执行速度成本)。然而,这仅在 iE 使用长(64 位)变量(或类似)的 32 位系统上很重要,在大多数其他情况下,设置/读取变量无论如何都是原子的。但是原子访问和原子操作之间有一个重要的区别。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。