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



我在Oracle文档中读到:

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

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

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

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

已弃用?一点也不。虽然基元变量的单个读写是原子的,但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在相关语句开始执行时不会更改,但可能会在执行开始后但在执行完成之前被另一个线程更改。

是否意味着AtomicInteger已被弃用,不应该在新项目中使用?

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

此外,AtomicInteger 和 primitive int 根本无法互换。有很多不同之处,但以下是浮现在脑海中的前三个:

  • AtomicInteger 可以通过引用传递,不像 primitive int,后者是按值传递的。
  • AtomicInteger有一些操作,例如compareAndSet(),这些操作在原语上不可用。
  • 即使是那些表面上看起来相同的,即AtomicInteger.getAndIncrement()vs。int++是不同的;前者是原子的,后者是两个不是原子在一起的操作。

我想这个功能已经添加到一些新的JDK版本中,因为我曾经认为所有原始变量的读取/写入都不是原子的

32 位或更小的基元的读写一直是原子的。

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

在该文档中使用术语原子与在AtomicInteger中使用的目的不同。

该文件还指出

原子动作不能交错,因此可以毫无畏惧地使用它们 的线程干扰。

这是指

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

thread 3保证看到值1或值2

但是,对于longdouble,您无法保证。Java 语言规范声明

出于 Java 编程语言内存模型的目的,一个 对非易失性longdouble值的单次写入被视为两个 单独写入:每个 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位,从而7fff_ffff_ffff_ffff创建long值。同样的情况也可能发生在double.

使用volatile修改longdouble变量可防止此行为。

最新更新