使用ConcurrentHashMap高效处理并发计数
在高并发场景下对ConcurrentHashMap进行写操作时,性能表现至关重要。本文通过对比两种实现方式,展示如何利用JDK内置的原子操作提升并发性能。
低效实现:锁保护下的条件更新
初始方案采用synchronized块包裹条件判断与赋值逻辑,虽然保证了线程安全,但存在明显性能瓶颈:
private Map<String, Long> normaluse() throws InterruptedException {
ConcurrentHashMap<String, Long> freqs = new ConcurrentHashMap<>(10);
ForkJoinPool forkJoinPool = new ForkJoinPool(10);
forkJoinPool.execute(() -> IntStream.rangeClosed(1, 10000000)
.parallel().forEach(i -> {
String key = "item" + ThreadLocalRandom.current().nextInt(10);
synchronized (freqs) {
if (freqs.containsKey(key)) {
freqs.put(key, freqs.get(key) + 1);
} else {
freqs.put(key, 1L);
}
}
}));
forkJoinPool.shutdown();
forkJoinPool.awaitTermination(1, TimeUnit.HOURS);
return freqs;
}
该方法的问题在于:
- 使用同步锁导致线程竞争
- 多次读取Map状态(containsKey、get)
- 非原子性操作无法避免中间状态冲突
高效优化:基于computeIfAbsent的原子化操作
改进方案引入LongAdder结合computeIfAbsent,将复合操作转化为单次原子操作:
private Map<String, Long> gooduse() throws InterruptedException {
ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>(10);
ForkJoinPool forkJoinPool = new ForkJoinPool(10);
forkJoinPool.execute(() -> IntStream.rangeClosed(1, 10000000)
.parallel().forEach(i -> {
String key = "item" + ThreadLocalRandom.current().nextInt(10);
freqs.computeIfAbsent(key, k -> new LongAdder()).increment();
}));
forkJoinPool.shutdown();
forkJoinPool.awaitTermination(1, TimeUnit.HOURS);
return freqs.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> e.getValue().longValue()));
}
关键优势:
computeIfAbsent底层依赖Unsafe的CAS原语,确保操作原子性- 无需显式加锁,避免线程阻塞
LongAdder专门优化高并发计数场景,减少争用- 操作合并为一次内存更新,降低开销
性能对比与验证
通过基准测试可观察到显著提升:
public String good() throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start("normaluse");
Map<String, Long> normaluse = normaluse();
stopWatch.stop();
Assert.isTrue(normaluse.size() == ITEM_COUNT, "normaluse size error");
Assert.isTrue(normaluse.entrySet().stream()
.mapToLong(item -> item.getValue()).reduce(0, Long::sum) == LOOP_COUNT,
"normaluse count error");
stopWatch.start("gooduse");
Map<String, Long> gooduse = gooduse();
stopWatch.stop();
Assert.isTrue(gooduse.size() == ITEM_COUNT, "gooduse size error");
Assert.isTrue(gooduse.entrySet().stream()
.mapToLong(item -> item.getValue())
.reduce(0, Long::sum) == LOOP_COUNT,
"gooduse count error");
log.info(stopWatch.prettyPrint());
return "OK";
}
实测结果表明,优化后的实现相比传统锁机制性能提升约10倍,且在大规模并发下仍保持稳定。
核心原理
JDK内部通过Unsafe.compareAndSetObject实现无锁更新:
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
此机制使得ConcurrentHashMap能够在不依赖锁的前提下完成高效的并发写入,是实现高性能并发数据结构的关键。