我看到Java的AtomicInteger如何在内部使用CAS(比较和交换)操作。基本上,当多个线程尝试更新值时,JVM在内部使用底层CAS机制并尝试更新该值。如果更新失败,则再次使用新值但不会阻止。
在Java8中,Oracle引入了一个新的类LongAdder,它在高争用下似乎比AtomicInteger表现更好。一些博客文章声称LongAdder通过维护内部单元格表现更好 - 这是否意味着LongAdder在内部聚合值并在以后更新?你能帮我理解LongAdder的工作原理吗?
答案 0 :(得分:9)
这是否意味着LongAdder在内部聚合值并在以后更新?
是的,如果我理解你的陈述。
Cell
中的每个LongAdder
都是AtomicLong
的变体。拥有多个这样的小区是一种分散争用的方式,从而提高吞吐量。
当要检索最终结果(总和)时,它只是将每个单元格的值加在一起。
关于如何组织细胞,如何分配细胞等的大部分逻辑可以在源中看到:http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/f398670f3da7/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java
特别是单元数量受CPU数量的限制:
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
答案 1 :(得分:4)
它的主要原因是"更快"是竞争性能。这很重要,因为:
在低更新争用下,这两个类具有相似的特征。
您使用LongAdder进行非常频繁的更新,其中原子CAS和对Unsafe
的本机调用会导致争用。 (参见source和volatile reads)。更不用说多个AtomicLongs上的cache misses/false sharing了(虽然我还没有看过类布局,但在实际的long
字段之前似乎没有足够的内存填充。
在高度争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
实现扩展Striped64
,它是64位值的数据持有者。这些值保存在单元格中,这些单元格用于填充(或条带化),因此也就是名称。在LongAdder上进行的每个操作都将修改Striped64中存在的值集合。发生争用时,会创建并修改新的单元格,因此旧的线程可以与争用的单元格同时完成。当您需要最终值时,每个单元格的总和就会加起来。
不幸的是,性能带来了成本,在这种情况下是内存(通常是这样)。如果在它上面抛出大量的线程和更新,Striped64会变得非常大。
引用来源: Javadoc for LongAdder
答案 2 :(得分:0)
Atomic Long使用CAS,它在激烈的竞争下会导致许多CPU周期浪费。
另一方面,当这些线程增加线程时,LongAdder使用非常巧妙的技巧来减少线程之间的争用。
因此,当我们调用increment()
时,LongAdder
在后台维护着一个可以按需增长的计数器数组。
因此,当更多线程正在调用crement()时,数组将更长。数组中的每个记录都可以单独更新-减少了争用。因此,LongAdder是从多个线程递增计数器的非常有效的方法。
在调用sum()
方法之前,LongAdder中计数器的结果不可用。