挥发性与原子

2022-08-31 07:16:31

我在线下的某个地方读到。

Java volatile关键字并不意味着原子,它常见的误解是,在声明volutionable之后,操作将是原子的,要使操作原子化,你仍然需要确保在Java中使用方法或块进行独占访问。++synchronized

那么,如果两个线程同时攻击一个基元变量,会发生什么呢?volatile

这是否意味着无论谁锁定它,都将首先设置其值。如果在此期间,当第一个线程更改其值时,其他一些线程出现并读取旧值,那么新线程不会读取其旧值吗?

原子关键字和易失性关键字有什么区别?


答案 1

关键字的效果大致是,对该变量的每个单独读取或写入操作都对所有线程以原子方式可见。volatile

然而,值得注意的是,需要多次读/写操作的操作(例如,等效于 ,它执行一次读取和一次写入)不是原子的,因为另一个线程可能在读取和写入之间写入。i++i = i + 1i

这些类(如 和 )以原子方式提供了更广泛的操作,特别是包括 的增量。AtomicAtomicIntegerAtomicReferenceAtomicInteger


答案 2

Volatile 和 Atomic 是两个不同的概念。Volatile 确保某个预期的(内存)状态在不同的线程中为真,而 Atomics 确保对变量的操作以原子方式执行。

以以下 Java 中的两个线程为例:

线程 A:

value = 1;
done = true;

线程 B:

if (done)
  System.out.println(value);

从 开始,线程规则告诉我们,线程 B 是否打印值是不确定的。此外,在这一点上也是未定义的!为了解释这一点,你需要了解一些关于Java内存管理(可能很复杂)的知识,简而言之:线程可能会创建变量的本地副本,而JVM可以对代码进行重新排序以优化它,因此不能保证上面的代码完全按照这个顺序运行。将完成设置为 true,然后将值设置为 1 可能是 JIT 优化的可能结果。value = 0done = false

volatile仅确保在访问此类变量时,新值将立即对所有其他线程可见,并且执行顺序可确保代码处于您期望的状态。因此,在上面的代码中,定义为易失性将确保每当线程B检查变量时,它要么是假的,要么是真的,如果它是真的,那么它也被设置为1。donevalue

作为易失性的副作用,此类变量的值以原子方式设置线程范围(执行速度的代价非常小)。然而,这仅在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。


推荐