并发给ConcurrentHashMap赋值

低性能

使用 ConcurrentHashMap 来统计,Key 的范围是 10。

使用最多 10 个并发,循环操作 1000 万次,每次操作累加随机的 Key。

如果 Key 不存在的话,首次设置值为 1。

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;
}

改进

使用 ConcurrentHashMap 的原子性方法 computeIfAbsent 来做复合逻辑操作,判断 Key 是否存在 Value,如果不存在则把 Lambda 表达式运行后的结果放入 Map 作为 Value,也就是新创建一个 LongAdder 对象,最后返回 Value。

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 使用 java 自带的 Unsafe 实现的 CAS。它在虚拟机层面确保了写入数据的原子性,比加锁的效率高得多:

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 的方式,性能提升 了 10 倍。

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";
}

posted on 2025-10-14 23:49  chuchengzhi  阅读(8)  评论(0)    收藏  举报

导航

杭州技术博主,专注分享云计算领域实战经验、技术教程与行业洞察, 打造聚焦云计算技术的垂直博客,助力开发者快速掌握云服务核心能力。

褚成志 云计算 技术博客