如果对 int 变量的写入和读取是原子的,为什么需要 AtomicInteger?

Why is AtomicInteger needed if writes and reads to int variables are atomic?

我在 Oracle docs 中读到:

  • Reads and writes are atomic for reference variables and for most
    primitive variables (all types except long and double).

(我想这个特性已经添加到一些新的 JDK 版本中,因为我曾经认为 reads/writes 所有原始变量都不是原子的)

这是否意味着 AtomicInteger 已弃用并且不应在新项目中使用?

弃用?一点也不。虽然原始变量的单个读取和写入是原子的,但 AtomicInteger(以及 java.util.concurrent.atomic 中的另一个原子 类)提供了更复杂的原子操作。这些包括 addAndGet(int) 之类的东西,它们对于原始 int 变量根本不是原子的。因此,

int i = 3;
AtomicInteger j = new AtomicInteger(3);
i += 5; // NOT thread-safe -- might not set i to 8
int n = j.addAndGet(5); // thread-safe -- always sets n to 8

(上面的两个评论都假设 ij 在相关语句 开始 执行时没有改变,但可能会在执行开始后但在完成之前被另一个线程更改。)

Does it mean that AtomicInteger is deprecated and shouldn't be used in new projects?

没有。首先也是最明显的,如果它被弃用,它会被标记为弃用。

此外,AtomicInteger 和 primitive int 根本不可互换。有很多差异,但这里是 spring 要记住的前三个:

  • AtomicInteger 可以通过引用传递,不像原始 int 那样通过值传递。
  • AtomicInteger 有一些操作,例如 compareAndSet(),在基元上不可用。
  • 即使表面上看起来相同,即 AtomicInteger.getAndIncrement()int++ 也不同;前者是原子的,第二个是两个不是原子的操作。

I guess this feature has been added in some new JDK release because I used to think that reads/writes of ALL primitive variables are NOT atomic

读取和写入 32 位或更小的基元始终是原子的。

虽然单个存储来自普通int的单个加载在Java中是原子的,你不能原子地增加它。这样做需要您首先加载该值,然后根据它计算新值,然后将新值存储回去。但是在两次访问之间,另一个线程可能已经修改了该值。 AtomicInteger 提供类似 getAndIncrement 的操作,无需使用锁即可用于此目的。

其他答案解决了为什么需要 AtomicInteger。我想澄清一下该文件的内容。

在该文档中使用术语 atomic 与其在 AtomicInteger.

中的用途不同

该文件还指出

Atomic actions cannot be interleaved, so they can be used without fear of thread interference.

这里指的是

int x;
x = 1; // thread 1
x = 2; // thread 2
System.out.println(x); // thread 3

thread 3 保证看到值 1 或值 2.

但是,对于 longdouble,您无法保证。 Java Language Specification 状态

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

所以,例如,

long x;
x = 0xffff_ffffL; // thread 1
x = 0x7fff_ffff_0000_0000L; // thread 2
System.out.println(x); // thread 3

thread 3 可以查看 thread 1 赋值的前 32 位和 thread 2 赋值的后 32 位,从而创建 long7fff_ffff_ffff_ffffdouble.

也会发生同样的情况

volatile 修改您的 longdouble 变量可以防止这种行为。