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