我在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
(上面的两个注释都是假设i
和j
在相关语句开始执行时不会更改,但可能会在执行开始后但在执行完成之前被另一个线程更改。
是否意味着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
。
但是,对于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位,从而7fff_ffff_ffff_ffff
创建long
值。同样的情况也可能发生在double
.
使用volatile
修改long
或double
变量可防止此行为。