我看到了Java的AtomicInteger是如何在内部使用CAS (比较和交换)操作的。基本上,当多线程尝试更新值时,JVM在内部使用底层CAS机制并尝试更新值。如果更新失败,则使用新值重试,但从不阻塞。
在Java8中,Oracle引入了一个新的类LongAdder,它在高争用情况下似乎比AtomicInteger表现得更好。一些博客文章声称,通过维护内部单元格,LongAdder的性能更好-这是否意味着LongAdder在内部聚合这些值并在以后更新它?你能帮我理解一下LongAdder是如何工作的吗?
发布于 2015-06-07 15:23:14
这是否意味着LongAdder在内部聚合这些值,并在以后更新它?
是的,如果我没理解错的话。
LongAdder中的每个Cell都是AtomicLong的变体。拥有多个这样的小区是分散争用并因此增加吞吐量的一种方式。
当要检索最终结果(sum)时,它只是将每个单元格的值相加。
关于如何组织单元格、如何分配单元格等的许多逻辑都可以在源代码中看到: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();发布于 2015-06-07 15:49:30
它“更快”的主要原因是它的争用性能。这一点很重要,因为:
在低更新争用情况下,这两个类具有相似的特征。
您将使用LongAdder进行非常频繁的更新,在这种情况下,对Unsafe的原子CAS和本机调用将导致争用。(请参见source和volatile reads)。更不用说多个AtomicLongs上的cache misses/false sharing了(虽然我还没有研究过类布局,但在实际的long字段之前似乎没有足够的内存填充。
在高争用情况下,该类的预期吞吐量明显更高,但代价是空间消耗更高。
该实现扩展了Striped64,它是64位值的数据保持器。这些值保存在单元格中,这些单元格是填充的(或带状的),因此得名。对LongAdder进行的每个操作都将修改Striped64中存在的值的集合。当争用发生时,一个新的单元格被创建和修改,因此旧的线程可以与争用的线程同时完成。当您需要最终值时,只需将每个单元格的总和相加即可。
不幸的是,性能是有代价的,在本例中是内存(通常如此)。如果向Striped64抛出大量线程和更新,它可能会变得非常大。
发布于 2019-04-25 11:30:49
Atomic使用CAS -在激烈的争用下会导致许多CPU周期的浪费。另一方面,LongAdder使用了一个非常聪明的技巧来减少线程之间的争用,当线程递增时。因此,当我们调用increment()时,LongAdder在幕后维护着一组可以按需增长的计数器。因此,当更多的线程调用increment()时,数组将变长。数组中的每条记录都可以单独更新,从而减少了争用。由于这一事实,LongAdder是从多个线程递增计数器的一种非常有效的方式。在我们调用sum()方法之前,LongAdder中计数器的结果是不可用的。
https://stackoverflow.com/questions/30691083
复制相似问题