一、常用的并发计数方法
1、synchronized
synchronized早期是一个重量级锁,因为线程竞争锁会引起操作系统用户态和内核态切换,浪费资源,效率不高。jdk1.6对 synchronized 做了性能优化,它会经历偏向锁,轻量级锁,最后才到重量级锁,性能提升显著。jdk1.7的 ConcurrentHashMap 是基于ReentrantLock的实现了锁,但在jdk1.8之后又替换成了 synchronized,籍此可见JVM团队对synchronized的信心;
2、原子更新整型 AtomicInteger、AtomicLong
JDK1.5提供了并发的Integer/Long的操作工具类 AtomicInteger、AtomicLong。AtomicLong底层是一个乐观锁,不用阻塞线程,其利用底层操作系统的CAS来保证原子性,即在一个死循环内不断执行CAS操作,直到成功。其缺陷是当并发较大时,大量线程不断CAS,存在多次的CAS操作失败,使CPU升高,效率降低。
AtomicLong value = new AtomicLong(0);
value.incrementAndGet();
3、LongAdder [单机]
在JDK8中新增了LongAdder,这是针对Long类型的数据的操作工具类。在ConcurrentHashMap中,对Map分割成多个segment,这样多个Segment的操作就可以并行执行,从而提高性能。在JDK8中,LongAdder与ConcurrentHashMap类似,将内部操作数据value分离成一个Cell数组,每个线程访问时,通过Hash等算法映射到其中一个Cell上。 计算最终的数据结果,则是各个Cell数组的累计求和。
4、Redisson分布式累加器 [分布式]
基于Redis的Redisson分布式长整型累加器(LongAdder)采用了与java.util.concurrent.atomic.LongAdder 类似的接口。通过利用客户端内置的 LongAdder 对象,为分布式环境下递增和递减操作提供了很高的性能。其性能最高比分布式 AtomicLong 对象快 10^4 倍。
RLongAddr itheimaLongAddr = redission.getLongAddr(“itheimaLongAddr”);
itheimaLongAddr.add(100);
itheimaLongAddr.increment();
itheimaLongAddr.increment();
itheimaLongAddr.sum();
基于Redis的Redisson分布式双精度浮点累加器(DoubleAdder)采用了与 java.util.concurrent.atomic.DoubleAdder 类似的接口。通过利用客户端内置的 DoubleAdder 对象,为分布式环境下递增和递减操作提供了很高的性能。其性能最高比分布式 AtomicDouble 对象快 10^4 倍。
RLongDouble itheimaDouble = redission.getLongDouble(“itheimaLongDouble”);
itheimaDouble.add(100);
itheimaDouble.increment();
itheimaDouble.increment();
itheimaDouble.sum();
二、并发计数方法性能测试
1、测试程序
private long count = 0;
@Test
public void counterCompare() {
this.counterCousum(1, 1000 * 1000);
this.counterCousum(20, 1000 * 1000);
this.counterCousum(40, 1000 * 1000);
this.counterCousum(60, 1000 * 1000);
this.counterCousum(100, 1000 * 1000);
}
/**
* @param threadCount 线程数量
* @param increTimes 每个线程自增次数
*/
private void counterCousum(int threadCount, int increTimes) {
long start;
try {
System.out.println(String.format("线程数量: %s, 自增次数: %s", threadCount, increTimes));
start = System.currentTimeMillis();
this.synchronizedTest(threadCount, increTimes);
System.out.println("Synchronized 耗时: " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
this.atomicLongTest(threadCount, increTimes);
System.out.println("AtomicLong 耗时: " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
this.longAdderTest(threadCount, increTimes);
System.out.println("LongAdder 耗时: " + (System.currentTimeMillis() - start) + "\n");
} catch (Exception e) {
e.printStackTrace();
}
}
private void atomicLongTest(int threadCount, int times) throws InterruptedException {
AtomicLong count = new AtomicLong();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
threadList.add(new Thread(()-> {
for (int j = 0; j < times; j++) {
count.incrementAndGet();
}
}));
}
for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
thread.join();
}
}
private void synchronizedTest(int threadCount, int times) throws InterruptedException {
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
threadList.add(new Thread(()-> {
for (int j = 0; j < times; j++) {
add();
}
}));
}
for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
thread.join();
}
}
private synchronized void add() {
count++;
}
private void longAdderTest(int threadCount, int times) throws InterruptedException {
LongAdder count = new LongAdder();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
threadList.add(new Thread(()-> {
for (int j = 0; j < times; j++) {
count.increment();
}
}));
}
for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
thread.join();
}
}
2、测试结果
synchronized、AtomicLong的耗时随着并发量的增大而增大,LongAdder的耗时保持相对稳定,性能优越 !!!