低性能
使用 ConcurrentHashMap 来统计,Key 的范围是 10。
使用最多 10 个并发,循环操作 1000 万次,每次操作累加随机的 Key。
如果 Key 不存在的话,首次设置值为 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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。它在虚拟机层面确保了写入数据的原子性,比加锁的效率高得多:
1 2 3
| 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 倍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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"; }
|