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

问题描述 投票:0回答:4

我在 Oracle 文档中读到:

  • 对于引用变量和大多数,读取和写入是原子
    原始变量(除 long 和 double 之外的所有类型)。

(我猜这个功能已经在一些新的 JDK 版本中添加了,因为我曾经认为所有原始变量的读/写都不是原子的)

这是否意味着

AtomicInteger
已被弃用并且不应在新项目中使用?

java multithreading atomic atomicinteger
4个回答
22
投票

虽然在 Java 中,单个存储或来自普通int的单个加载

是原子的,但您不能原子地(比如说)递增它。这样做需要您首先加载该值,然后根据该值计算新值,然后将新值存储回来。但在两次访问之间,另一个线程可能修改了该值。 
AtomicInteger
 提供类似 
getAndIncrement
 的操作,无需使用锁即可用于此目的。


7
投票
已弃用?一点也不。虽然原始变量(非易失性

long

double
 除外)的单独读取和写入是原子的,但 
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
(上面的两条注释都假设 

i

j
 在相关语句 
starts 执行时没有更改,但可能会在执行开始后但完成之前被另一个线程更改。 )


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

不。首先也是最明显的,如果它被弃用,它将被标记为弃用。

此外,AtomicInteger 和原始 int 根本不能互换。有很多差异,但首先想到的是以下三个:

    AtomicInteger 可以通过引用传递,与按值传递的原始 int 不同。
  • AtomicInteger 有一些操作,例如
  • compareAndSet()
    ,在基元上不可用。 
  • 即使那些表面上看起来相同的,即
  • AtomicInteger.getAndIncrement()
    int++
    ,也是不同的;前者是原子的,第二个是两个非原子操作在一起。
我猜这个功能已经在一些新的 JDK 版本中添加了,因为我曾经认为所有原始变量的读/写都不是原子的

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


3
投票
其他答案解决了为什么需要

AtomicInteger

。我想澄清一下该文件的内容。

该文档中使用术语

atomic 的目的与 AtomicInteger

 中使用的目的不同。

该文件还指出

原子动作不能交错,因此可以放心使用 螺纹干涉。

这里指的是

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

thread 3

 保证看到值 
1
 或值 
2

但是,对于

long

double
,您没有这样的保证。 
Java 语言规范 规定

就 Java 编程语言内存模型而言, 对非易失性

long

double
 值的单次写入被视为两次
  单独写入:每个 32 位半写入一个。这可能会导致
  线程看到 64 位值的前 32 位的情况
  一次写入,第二个 32 位来自另一次写入。

例如,

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 位,从而创建 
long
7fff_ffff_ffff_ffff
double
 也会发生同样的情况。

使用

long

 修改 
double
volatile
 变量可以防止这种行为。

© www.soinside.com 2019 - 2024. All rights reserved.