AtomicInteger如何实现线程安全

3

我在阅读有关Java中原子变量的文档。通常情况下,AtomicInteger被认为是线程安全的。

根据我的理解,原子整数的工作原理是基于比较与交换算法。但我不理解,如果两个线程同时尝试增加同一个原子变量会怎么样。

例如,我定义了AtomicInteger var = 1,并且这被线程Thread_1Thread_2使用。当这两个线程尝试在同一时间T1var进行递增时,会发生什么? 我知道这将是很少见的情况,但如果真的发生了呢?在比较和交换中,它会在单个原子操作中读取和更新变量,并从内存中检查值。那么如果在时间T1-1时,var的值为5,并且Thread1Thread2都开始递增它,会发生什么?哪一个会失败?这将是随机行为吗?或者我漏掉了一些非常基本的东西。


1
我知道这很罕见,但罕见程度完全取决于情况。在Java 8中提供了LongAdder,以提供一种比AtomicLong更少竞争的方式来在多个线程中递增长整型。 - Andy Turner
3个回答

8

比较并交换在CPU级别上是原子性的。

你可以使用比较并交换显式地实现增量操作:

int value;
do { 
  value = var.get();
} while (!var.compareAndSwap(value, value + 1));
compareAndSwap被CPU保证是原子性的(会有本地实现)。如果两个线程同时命中这个compareAndSwap,只有一个线程会“获胜”,并在compareAndSwap调用的结果中收到true,因此循环停止。另一个线程将“失败”,并收到false作为结果,因此它将再次循环:它读取一个新值,然后再次尝试CAS。如果这成功了(因为没有其他线程正在尝试同时执行它,或者它“赢”了另一个线程),循环就停止了;否则,它会再次尝试。

在JNI级别上,“compareAndSetLong()”操作只能保证一次由一个线程运行吗?因此,两个线程不能同时访问“compareAndSetLong()”吗? - user2606235
@user2606235 "所以两个线程不能同时访问“compareAndSetLong()”" 是正确的。 - Andy Turner

3
如果在T1-1时刻,变量的值为5,Thread1和Thread2都开始对其进行递增,那怎么办呢?其中一个线程会成功使用当前值为5和新值为6执行比较并交换操作,而另一个线程会失败并尝试使用值为6和新值为7再次尝试。它们不能同时使用当前值为5和新值为6进行操作,这在CPU级别上得到了处理。

即使它们在不同的核心上运行且CAS操作在完全相同的时间执行,仍然会有一个操作失败,哪一个操作失败实际上是随机的(可能取决于CPU实现)。


我无法理解这在多核系统中如何工作。 - Geek_To_Learn
这只是CPU所做的事情,将其视为黑匣子。如果您想了解如何准确完成此操作,您需要学习CPU架构(从阅读https://www.amazon.com/Computer-Architecture-Quantitative-John-Hennessy/dp/012383872X开始)。 - Oleg

2
这是一个典型的竞态条件情况。将这个增量操作视为一个小房间,里面有一扇小门,只能容纳一个人并执行一些操作。前两个成功进入这个房间的线程中的第一个将执行增量操作。当它完成后,“不太幸运”的第二个线程将执行其工作。但重要的是,两个操作的结果是一致的,因为它们不会同时执行增量操作。原始答案:最初的回答

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