原子类
在多线程环境下,常用累加操作方式是使用原子类进行累加,例如AtomicInteger、AtomicLong。但是使用原子类在多线程高竞争的情况下,CAS会经常失败,并发效率会大大降低。
因为CAS操作失败后要自旋再次进行替换,这样失败的线程就会大量消耗CPU资源。所以在高并发的场景下使用原子类累加器并不是很好的选择。
Striped64
Striped64是一种高并发累加器,有效解决了原子类累加的弊端。Striped64将线程竞争的操作分散开来,每个线程操作一个cell,而sum则等于base和所有cell值的和。
性能比较
开启10个线程,并发执行累加操作,每个线程加10000000。
- 使用AtomicLong
public static void main(String[] args) {AtomicLong atomicLong = new AtomicLong(0);CyclicBarrier cyclicBarrier = new CyclicBarrier(10);CountDownLatch countDownLatch = new CountDownLatch(10);long l = System.currentTimeMillis();for (int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}int i=10000000;while(i>0){atomicLong.incrementAndGet();i--;}countDownLatch.countDown();}}).start();}try {countDownLatch.await();long l2 = System.currentTimeMillis();System.out.println(l2-l);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicLong.get());}
计算结果:
1964ms
100000000
- 使用LongAdder
public static void main(String[] args) {LongAdder longAdder = new LongAdder();CyclicBarrier cyclicBarrier = new CyclicBarrier(10);CountDownLatch countDownLatch = new CountDownLatch(10);long l = System.currentTimeMillis();for (int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}int i=10000000;while(i>0){longAdder.add(1);i--;}countDownLatch.countDown();}}).start();}try {countDownLatch.await();long l2 = System.currentTimeMillis();System.out.println((l2-l)+"ms");} catch (InterruptedException e) {e.printStackTrace();}System.out.println(longAdder.sum());}
执行结果:
266ms
100000000
比较两者的性能,Striped64比原子类要快约10倍。
LongAdder
当线程来进行添加操作的,会根据线程ID定位到具体的cell,线程再对cell进行CAS操作,进行累加。这样各个线程就不用产生激烈竞争导致频繁CAS失败。对于JVM中最高并发线程数等与机器可用CPU核数,所以cells数组的长度也不会很长,进行数组求和也很快。
Striped64的继承类有LongAdder,以LongAdder累加流程为例: