- 使用 ConcurrentHashMap 来统计,Key 的范围是 10。
- 使用最多 10 个并发,循环操作 1000 万次,每次操作累加随机的 Key。
- 如果 Key 不存在的话,首次设置值为 1
-
- //循环次数
- private static int LOOP_COUNT = 10000000;
- //线程数量
- private static int THREAD_COUNT = 10;
- //元素数量
- private static int ITEM_COUNT = 10;
- private Map<String, Long> normaluse() throws InterruptedException {
- ConcurrentHashMap<String, Long> freqs = new ConcurrentHashMap<>(ITEM_COUNT);
- ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_COUNT);
- forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_COUNT).parallel().forEach(i -> {
- //获得一个随机的Key
- String key = "item" + ThreadLocalRandom.current().nextInt(ITEM_COUNT);
- synchronized (freqs) {
- if (freqs.containsKey(key)) {
- //Key存在则+1
- freqs.put(key, freqs.get(key) + 1);
- } else {
- //Key不存在则初始化为1
- freqs.put(key, 1L);
- }
- }
- }
- ));
- forkJoinPool.shutdown();
- forkJoinPool.awaitTermination(1, TimeUnit.HOURS);
- return freqs;
- }
直接通过锁的方式锁住 Map,然后做判断、读取现在的累计值、加 1、保存累加后值的逻辑。这段代码在功能上没有问题,但无法充分发挥 ConcurrentHashMap 的威力
- private Map<String, Long> gooduse() throws InterruptedException {
- ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>(ITEM_COUNT);
- ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_COUNT);
- forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_COUNT).parallel().forEach(i -> {
- String key = "item" + ThreadLocalRandom.current().nextInt(ITEM_COUNT);
- //利用computeIfAbsent()方法来实例化LongAdder,然后利用LongAdder来进行线程安全计数
- freqs.computeIfAbsent(key, k -> new LongAdder()).increment();
- }
- ));
- forkJoinPool.shutdown();
- forkJoinPool.awaitTermination(1, TimeUnit.HOURS);
- //因为我们的Value是LongAdder而不是Long,所以需要做一次转换才能返回
- return freqs.entrySet().stream()
- .collect(Collectors.toMap(
- e -> e.getKey(),
- e -> e.getValue().longValue())
- );
- }
这段改进后的代码中,我们巧妙利用了下面两点:
- 使用 ConcurrentHashMap 的原子性方法 computeIfAbsent 来做复合逻辑操作,判断 Key 是否存在 Value,如果不存在则把 Lambda 表达式运行后的结果放入 Map 作为 Value,也就是新创建一个 LongAdder 对象,最后返回 Value。
- 由于 computeIfAbsent 方法返回的 Value 是 LongAdder,是一个线程安全的累加器,因此可以直接调用其 increment 方法进行累加。
这样在确保线程安全的情况下达到极致性能,把之前 7 行代码替换为了 1 行。