1.在Java8中,Doug Lea大师在java.util.concurrent.atomic包中添加了几个新的类,其中有一个是LongAdder。
2.如下代码,对比LongAdder和AtomicLong的性能
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* 对比LongAdder和AtomicLong的性能
* @author shixiangcheng
* 2019-12-20
*/
public class TestAdder {
public static void main(String [] args) {
//循环5次,取平均值
for(int i=0;i<5;i++) {
demo(()->new LongAdder(),adder->adder.increment());
}
for(int i=0;i<5;i++) {
demo(()->new AtomicLong(),adder->adder.getAndIncrement());
}
}
private static<T> void demo(Supplier<T> adderSupplier,Consumer<T> action) {
T adder=adderSupplier.get();
long start=System.currentTimeMillis();
List<Thread> ts=new ArrayList<Thread>();
//10个线程,每个累加10万
for(int i=0;i<10;i++) {
ts.add(new Thread(()->{
for(int j=0;j<100000;j++) {
action.accept(adder);
}
}));
}
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end=System.currentTimeMillis();
System.out.println(adder+" cost: "+(end-start)+"ms");
}
}
执行结果如下:
1000000 cost: 23ms
1000000 cost: 5ms
1000000 cost: 5ms
1000000 cost: 5ms
1000000 cost: 4ms
1000000 cost: 30ms
1000000 cost: 26ms
1000000 cost: 26ms
1000000 cost: 26ms
1000000 cost: 27ms
前5条是LongAdder的耗时情况,后5条是AtomicLong的耗时情况,对比发现LongAdder的性能优于AtomicLong。
分析:
在并发程度较高时,AtomicLong使用的CAS操作失败频率也较高,且多了很多不必要的资源消耗,导致性能下降。考虑到AtomicLong中CAS竞争的资源单一,选择在有冲突时分散竞争资源,为每个线程分配一个Cell,让每个资源竞争对应的资源,大幅减少冲突。
类似并发容器ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。在Java8之后,ConcurrentHashMap放弃了分段锁的机制,改为采用CAS+Volatile的实现机制。
总结:LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。
如果觉得文章好,别忘了给我点赞哦。老师说,点赞的人最美!