10

我在Oracle 文档中读到:

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

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

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

4

4 回答 4

20

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

于 2017-02-05T16:42:58.533 回答
7

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

于 2017-02-05T16:41:38.113 回答
5

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

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

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

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

我猜这个特性已经被添加到一些新的 JDK 版本中,因为我曾经认为所有原始变量的读/写都不是原子的

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

于 2017-02-05T16:41:50.943 回答
3

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

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

该文件还指出

原子动作不能交错,因此可以使用它们而不必担心线程干扰。

这是指

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

thread 3保证看到 value1或 value 2

但是,使用longor double,您没有该保证。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 位和分配的后 32 位thread 2,从而创建long7fff_ffff_ffff_ffff。同样的情况也可能发生在double.

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

于 2017-02-05T17:00:04.100 回答