LongAdder
本文转载自占小狼
如果让你实现一个计数器, 有点经验的同学可以很快的想到使用 AtomicInteger 或者 AtomicLong 进行简单的封装.
因为计数器操作涉及到内存的可见性和线程之间的竞争, 而 Atomic * 的实现完美的屏蔽了这些技术细节, 我们只需要执行相应的方法, 就能实现对应的业务需求.
Atomic 虽然好用, 不过这些的操作在并发量很大的情况下, 性能问题也会被相应的放大. 我们可以先看下其中 getAndIncrement 的实现代码
- public final long getAndIncrement() {
- return unsafe.getAndAddLong(this, valueOffset, 1L);
- }
- // unsafe 类中的实现
- public final long getAndAddLong(Object var1, long var2, long var4) {
- long var6;
- do {
- var6 = this.getLongVolatile(var1, var2);
- } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
- return var6;
- }
很显然, 在 getAndAddLong 实现中, 为了实现正确的累加操作, 如果并发量很大的话, CPU 会花费大量的时间在试错上面, 相当于一个 spin 的操作. 如果并发量小的情况, 这些消耗可以忽略不计.
既然已经意识到 Atomic*** 有这样的业务缺陷, Doug Lea 大神又给我们提供了 LongAdder, 内部的实现有点类似 ConcurrentHashMap 的分段锁, 最好的情况下, 每个线程都有独立的计数器, 这样可以大量减少并发操作.
下面通过 JMH 比较一下 AtomicLong 和 LongAdder 的性能.
- @OutputTimeUnit(TimeUnit.MICROSECONDS)
- @BenchmarkMode(Mode.Throughput)
- public class Main {
- private static AtomicLong count = new AtomicLong();
- private static LongAdder longAdder = new LongAdder();
- public static void main(String[] args) throws Exception {
- Options options = new OptionsBuilder().include(Main.class.getName()).forks(1).build();
- new Runner(options).run();
- }
- @Benchmark
- @Threads(10)
- public void run0(){
- count.getAndIncrement();
- }
- @Benchmark
- @Threads(10)
- public void run1(){
- longAdder.increment();
- }
- }
1, 设置 BenchmarkMode 为 Mode.Throughput, 测试吞吐量
2, 设置 BenchmarkMode 为 Mode.AverageTime, 测试平均耗时
线程数为 1
1, 吞吐量
- Benchmark Mode Cnt Score Error Units
- Main.run0 thrpt 5 154.525 ± 9.767 ops/us
- Main.run1 thrpt 5 89.599 ± 7.951 ops/us
2, 平均耗时
- Benchmark Mode Cnt Score Error Units
- Main.run0 avgt 5 0.007 ± 0.001 us/op
- Main.run1 avgt 5 0.011 ± 0.001 us/op
单线程情况:
1,AtomicLong 的吞吐量和平均耗时都占优势
线程数为 10
1, 吞吐量
- Benchmark Mode Cnt Score Error Units
- Main.run0 thrpt 5 37.780 ± 1.891 ops/us
- Main.run1 thrpt 5 464.927 ± 143.207 ops/us
2, 平均耗时
- Benchmark Mode Cnt Score Error Units
- Main.run0 avgt 5 0.290 ± 0.038 us/op
- Main.run1 avgt 5 0.021 ± 0.001 us/op
并发线程为 10 个时:
LongAdder 的吞吐量比较大, 是 AtomicLong 的 10 倍多.
LongAdder 的平均耗时是 AtomicLong 的十分之一.
线程数为 30
1, 吞吐量
- Benchmark Mode Cnt Score Error Units
- Main.run0 thrpt 5 36.215 ± 2.341 ops/us
- Main.run1 thrpt 5 486.630 ± 26.894 ops/us
2, 平均耗时
- Main.run0 avgt 5 0.792 ± 0.021 us/op
- Main.run1 avgt 5 0.063 ± 0.002 us/op
线程数为 30 个时:
LongAdder 的吞吐量比较大, 也是 AtomicLong 的 10 倍多.
LongAdder 的平均耗时也是 AtomicLong 的十分之一.
总结
一些高并发的场景, 比如限流计数器, 建议使用 LongAdder 替换 AtomicLong, 性能可以提升不少.
关注我, 这里只有干货!
来源: http://www.jianshu.com/p/7aff24e56309