如果对 int 变量的写入和读取是原子的,为什么需要 AtomicInteger?
Why is AtomicInteger needed if writes and reads to int variables are atomic?
我在 Oracle docs 中读到:
- Reads and writes are atomic for reference variables and for most
primitive variables (all types except long and double).
(我想这个特性已经添加到一些新的 JDK 版本中,因为我曾经认为 reads/writes 所有原始变量都不是原子的)
这是否意味着 AtomicInteger
已弃用并且不应在新项目中使用?
弃用?一点也不。虽然原始变量的单个读取和写入是原子的,但 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
在相关语句 开始 执行时没有改变,但可能会在执行开始后但在完成之前被另一个线程更改。)
Does it mean that AtomicInteger is deprecated and shouldn't be used in new projects?
没有。首先也是最明显的,如果它被弃用,它会被标记为弃用。
此外,AtomicInteger 和 primitive int 根本不可互换。有很多差异,但这里是 spring 要记住的前三个:
- AtomicInteger 可以通过引用传递,不像原始 int 那样通过值传递。
- AtomicInteger 有一些操作,例如
compareAndSet()
,在基元上不可用。
- 即使表面上看起来相同,即
AtomicInteger.getAndIncrement()
与 int++
也不同;前者是原子的,第二个是两个不是原子的操作。
I guess this feature has been added in some new JDK release because I used to think that reads/writes of ALL primitive variables are NOT atomic
读取和写入 32 位或更小的基元始终是原子的。
虽然单个存储或来自普通int
的单个加载在Java中是原子的,你不能原子地增加它。这样做需要您首先加载该值,然后根据它计算新值,然后将新值存储回去。但是在两次访问之间,另一个线程可能已经修改了该值。 AtomicInteger
提供类似 getAndIncrement
的操作,无需使用锁即可用于此目的。
其他答案解决了为什么需要 AtomicInteger
。我想澄清一下该文件的内容。
在该文档中使用术语 atomic 与其在 AtomicInteger
.
中的用途不同
该文件还指出
Atomic actions cannot be interleaved, so they can be used without fear
of thread interference.
这里指的是
int x;
x = 1; // thread 1
x = 2; // thread 2
System.out.println(x); // thread 3
thread 3
保证看到值 1
或值 2
.
但是,对于 long
或 double
,您无法保证。 Java Language Specification 状态
For the purposes of the Java programming language memory model, a
single write to a non-volatile long
or double
value is treated as two
separate writes: one to each 32-bit half. This can result in a
situation where a thread sees the first 32 bits of a 64-bit value from
one write, and the second 32 bits from another write.
所以,例如,
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
.
也会发生同样的情况
用 volatile
修改您的 long
或 double
变量可以防止这种行为。
我在 Oracle docs 中读到:
- Reads and writes are atomic for reference variables and for most
primitive variables (all types except long and double).
(我想这个特性已经添加到一些新的 JDK 版本中,因为我曾经认为 reads/writes 所有原始变量都不是原子的)
这是否意味着 AtomicInteger
已弃用并且不应在新项目中使用?
弃用?一点也不。虽然原始变量的单个读取和写入是原子的,但 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
在相关语句 开始 执行时没有改变,但可能会在执行开始后但在完成之前被另一个线程更改。)
Does it mean that AtomicInteger is deprecated and shouldn't be used in new projects?
没有。首先也是最明显的,如果它被弃用,它会被标记为弃用。
此外,AtomicInteger 和 primitive int 根本不可互换。有很多差异,但这里是 spring 要记住的前三个:
- AtomicInteger 可以通过引用传递,不像原始 int 那样通过值传递。
- AtomicInteger 有一些操作,例如
compareAndSet()
,在基元上不可用。 - 即使表面上看起来相同,即
AtomicInteger.getAndIncrement()
与int++
也不同;前者是原子的,第二个是两个不是原子的操作。
I guess this feature has been added in some new JDK release because I used to think that reads/writes of ALL primitive variables are NOT atomic
读取和写入 32 位或更小的基元始终是原子的。
虽然单个存储或来自普通int
的单个加载在Java中是原子的,你不能原子地增加它。这样做需要您首先加载该值,然后根据它计算新值,然后将新值存储回去。但是在两次访问之间,另一个线程可能已经修改了该值。 AtomicInteger
提供类似 getAndIncrement
的操作,无需使用锁即可用于此目的。
其他答案解决了为什么需要 AtomicInteger
。我想澄清一下该文件的内容。
在该文档中使用术语 atomic 与其在 AtomicInteger
.
该文件还指出
Atomic actions cannot be interleaved, so they can be used without fear of thread interference.
这里指的是
int x;
x = 1; // thread 1
x = 2; // thread 2
System.out.println(x); // thread 3
thread 3
保证看到值 1
或值 2
.
但是,对于 long
或 double
,您无法保证。 Java Language Specification 状态
For the purposes of the Java programming language memory model, a single write to a non-volatile
long
ordouble
value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
所以,例如,
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
.
用 volatile
修改您的 long
或 double
变量可以防止这种行为。