Java中的Atomic Integer和普通的不可变Integer类有什么区别?

18
整数类(Integer class)也是不可变类(immutable class),而我们知道不可变类是线程安全的,那么为什么还需要原子整数(Atomic Integer)呢?我感到困惑。这是因为不可变对象的读写操作不需要是原子性的,但原子整数的读写操作是原子性的。这意味着原子类也是线程安全的。

什么时候使用哪个类? - user18424
5
一个可变,一个不可变。所以当你需要可变的值时,可以使用AtomicInteger(还有其他选项),但不能使用Integer - Andy Turner
2
当您需要一个线程安全的可变值而不仅仅是一个可变值时,可以使用AtomicInteger - eduardogr
+1 @EduGR 来指定线程安全的可变值。原子类中的类(如AtomicInteger)基于易变变量的原理,在内存中进行读/写操作以在多线程环境下保留更改。 - ankidaemon
5个回答

11

AtomicInteger是在多线程环境下使用的,当您需要确保只有一个线程可以更新int变量时使用。优点是不需要外部同步,因为修改它值的操作以线程安全的方式执行。

考虑以下代码:

private int count;

public int updateCounter() {
   return ++count;
}
如果有多个线程调用updateCounter方法,有可能其中一些线程会收到相同的值。其原因是++count操作不是原子性的,因为它不只是一个操作,而是由三个操作组成:读取计数器,将1加到这个值上,然后将其写回。多个调用线程可能会看到变量为未修改为最新值。
应该使用以下代码替换上述代码:
private AtomicInteger count = new AtomicInteger(0);
public int updateCounter() {
    return count.incrementAndGet();
}

incrementAndGet方法保证原子地递增存储的值并在不使用任何外部同步的情况下返回其值。

如果您的值从未更改,则无需使用AtomicInteger,int足以使用。


6

AtomicInteger是线程安全的(事实上,java.util.concurrent.atomic包中的所有类都是线程安全的),而普通整数则不是线程安全的。

当您在多线程环境中使用'Integer'变量时(使其线程安全),您需要使用'synchronized'和'volatile'关键字,而对于原子整数,您不需要使用'synchronized'和'volatile'关键字,因为原子整数会处理线程安全性。

此外,我建议您参考下面有关相同主题的有用教程: http://tutorials.jenkov.com/java-concurrency/compare-and-swap.html

请参考下面的oracle文档以获取有关'atomic'包的更多信息: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html


1
普通整数只有在与“synchronized”和“volatile”关键字一起使用时才是线程安全的。 - developer
@开发者 Integer是不可变的,因此默认情况下是线程安全的,无需使用synchronized或volatile。然而,不可变性的缺点是值永远不能改变。如果需要一个可变的值,请使用AtomicInteger或其他方法或volatile/synchronized关键字。 - puhlen

3

虽然不可变对象在定义上是线程安全的,但可变对象也可以是线程安全的。

这正是Atomic...类(如AtomicIntegerAtomicBoolean等)的目的所在。

各种...get......set...方法允许线程安全地访问和修改对象。

毫不奇怪,该类声明在java.util.concurrent包中。

您只需浏览java.util.concurrent.atomic包的API

一组支持单变量无锁线程安全编程的小型工具类。


原子类应该比不可变的包装类具有更好的性能,我认为。 - user18424
3
@user18424 既然它们不能用于同一目的,那么您的想法是错误的。在这两个类别之间比较性能并没有逻辑上的依据。 - Kayaman
@user18424 不一定。在单线程环境中,你实际上应该坚持使用 Number。这完全取决于用途。 - Mena

2

考虑一个变量

int myInt = 3;

AtomicIntegermyInt相关联。

Integer3相关联。

换句话说,您的变量是可变的,可以更改其值。而值为3的整数文字是一个常量、不可变表达式。

整数是字面量的对象表示形式,因此是不可变的,基本上只能读取它们。

AtomicInteger是这些值的容器。您可以读取和设置它们,就像将值分配给变量一样。但与更改int变量的值不同,对AtomicInteger的操作是原子的。

例如,以下操作不是原子性的:

if(myInt == 3) {
   myInt++; 
}

这是原子性。
AtomicInteger myInt = new AtomicInteger(3);

//atomic
myInt.compareAndSet(3, 4);

虽然这个答案似乎包含了大致的意思,但 AtomicInteger 无法引用 int 变量,因此你在前三行的示例只会让人感到困惑。 - Kayaman
抱歉,我不是母语者。我指的“refer”并不是指“Java引用”的意思,而是更多地类似于“牛与奶的关系如同羊与羊毛的关系”,但也许在这种情况下“refer”不是正确的词,使用“relate”会更好吗? - Gerald Mücke

-1

我认为AtomicInteger和普通的不可变整数之间的主要区别将在我们理解为什么即使是不可变整数也不是线程安全时显现出来。

让我们通过一个例子来看看。

假设我们有一个值为int count = 5的变量,它被两个名为T1T2的不同线程共享,两个线程同时读取和写入。

我们知道,如果将任何值重新分配给不可变对象,则旧对象仍然留在池中,新对象接管。

现在,当T1和T2将其值更新到计数变量中时,Java可能会将此值带入某些缓存中,并在那里执行设置操作,我们不知道JVM何时将更新后的值写入主内存,因此可能存在一个线程将值更新为完全过时的值的可能性。

这就引出了volatile关键字。

volatile - 此关键字确保所有对任何变量的I/O操作都将在主内存上进行,以便所有线程都使用最新的值。

考虑一下,如果有1个线程在写入,而所有其他线程都在读取,则volatile将解决我们的问题,但是如果所有线程都在同时读写同一个变量,则需要同步以确保线程安全。 volatile关键字不能确保线程安全。 现在,让我们来看看为什么要使用AtomicIntegers。即使我们使用synchronized关键字来确保线程安全,计数变量的实际更新操作也将是一个三步过程。
  1. 获取计数变量的更新值
  2. 将该值增加1
  3. 将该值设置为计数变量
这就是为什么在考虑线程安全性时,普通整数更新任何值需要稍微长一点时间的原因。 AtomicIntegers通过优化的无锁算法Compare-And-Swap(CAS方法)进一步解决了这个问题,并且可以更快地更新。 它们将所有更新操作作为单步过程进行原子操作。

不变对象是线程安全的,但为什么? 实际上,不变对象始终是线程安全的,因为它们的状态不能被更改,并且在多个线程中使用时不会导致竞态条件。 但是,请注意,其引用可能不安全。 回到基础知识:线程安全意味着一个对象在任何给定时间都可以由另一个线程执行而不会出现问题。 换句话说,如果一个或多个线程正在访问一个对象,那么这些线程可以在没有干扰的情况下执行此操作,因为不会有其他线程执行相同的操作。 - Debashis
AtomicInteger仍然需要加载/增量/存储,只是将这三个步骤捆绑成原子RMW事务。(在一些CPU上,如x86或ARMv8.1,使用一个特殊的指令来执行完整的fetch_add操作。不是CAS重试循环,那样会更慢。但在许多其他ISA上,它确实需要LL/SC重试循环。) 它比单独的加载/增量/存储慢,特别是对于已经在L1d缓存中热点值,因为CPU必须做额外的工作,以确保它在内部加载/添加/存储操作的持续时间内挂起该缓存行。 - Peter Cordes
当然,Java的volatile意味着顺序一致性,因此递增一个volatile就意味着进行了顺序一致性存储。在x86上,这意味着完整的屏障,与lock add的成本大致相同。但是在ARMv8.0上,例如,AtomicInteger递增将需要一个ldaxr/stlxr重试循环,而volatile加载/ volatile存储只需要一个非循环的ldar(顺序获取加载)/stlr(释放存储)。 https://godbolt.org/z/57nfTqEcr 显示了C代码的编译等效性。更多指令并不总是意味着更慢;单独的原子加载和原子存储类似于普通的加载/存储。 - Peter Cordes
原子增量比在单个增量周围采取单独的锁(“synchronized”)更快。 锁定意味着您可以在一个关键部分内廉价地对同一变量执行多个操作,但是如果您只需要对一个变量执行一件事,则原子性很好。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接