我在Oracle 文档中读到:
- 对于引用变量和大多数 原始变量(除了 long 和 double 之外的所有类型),读取和写入都是原子的。
(我猜这个特性已经被添加到一些新的 JDK 版本中,因为我曾经认为所有原始变量的读/写都不是原子的)
这是否意味着AtomicInteger
已弃用且不应在新项目中使用?
我在Oracle 文档中读到:
- 对于引用变量和大多数 原始变量(除了 long 和 double 之外的所有类型),读取和写入都是原子的。
(我猜这个特性已经被添加到一些新的 JDK 版本中,因为我曾经认为所有原始变量的读/写都不是原子的)
这是否意味着AtomicInteger
已弃用且不应在新项目中使用?
虽然在 Java 中单个存储或单个加载是int
原子的,但您不能原子地增加它。这样做需要您首先加载该值,然后根据它计算新值,然后将新值存储回来。但是在两次访问之间,另一个线程可能已经修改了该值。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 和原始 int 根本不可互换。有很多不同之处,但首先想到的是以下三个:
compareAndSet()
AtomicInteger 有一些在原语上不可用的操作,例如。AtomicInteger.getAndIncrement()
vs.int++
也是不同的;前者是原子的,第二个是两个不是原子的操作在一起。我猜这个特性已经被添加到一些新的 JDK 版本中,因为我曾经认为所有原始变量的读/写都不是原子的
32 位或更小的原语的读写一直是原子的。
其他答案解决了为什么AtomicInteger
需要。我想澄清该文件在说什么。
在该文档中使用术语atomic与在AtomicInteger
.
该文件还指出
原子动作不能交错,因此可以使用它们而不必担心线程干扰。
这是指
int x;
x = 1; // thread 1
x = 2; // thread 2
System.out.println(x); // thread 3
thread 3
保证看到 value1
或 value 2
。
但是,使用long
or 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 位和分配的后 32 位thread 2
,从而创建long
值7fff_ffff_ffff_ffff
。同样的情况也可能发生在double
.
修改您的long
ordouble
变量可以volatile
防止这种行为。