首页 > 其他分享 >ConcurrentHashMap使用案例(单词数量统计)

ConcurrentHashMap使用案例(单词数量统计)

时间:2023-06-22 21:00:28浏览次数:45  
标签:200 ConcurrentHashMap word map 单词 案例 线程 words new

前言

  • 目标:实现单词数量统计
  • 过程:首先使用26个英文字母,每个字母200个,将26200个字母打乱顺序存入26个txt文件中。
    使用26个线程,每个线程统计一个txt文件的200个字母。26个线程同时操作这一个Map集合。
    最终想要得到的结果为:a:200(a被统计了200次),b:200(b被统计了200次)……z:200(z被统计200次)总和26
    200。
  • 实践:使用HashMap进行统计
  • 问题:HashMap会出现什么样的结果?
  • 如何使用ConcurrentHashMap解决此问题。

1. 案例样本准备

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
 
public class WordCountCreat {
    static final String ALPHA = "abcedfghijklmnopqrstuvwxyz"; //26个英文字母
    public static void main(String[] args) throws IOException {
        int length = ALPHA.length();//26
        int count = 200;
        List<String> list = new ArrayList<>(length * count);//26*200
        for (int i = 0; i < length; i++) {//每个英文字母生成200个加入list
            char ch = ALPHA.charAt(i);
            for (int j = 0; j < count; j++) {
                list.add(String.valueOf(ch));
            }
        }
        Collections.shuffle(list);//将26*200个字母打乱顺序
 
        for (int i = 0; i < 26; i++) { //每200个字母为一组,存入26个txt文件
            String path = "tmp/" + (i + 1) + ".txt";
            File file = new File(path);
            if (!file.isFile()) {
                file.createNewFile();
            }
            BufferedWriter writer = new BufferedWriter(new FileWriter(path));
            String collect = list.subList(i * count, (i + 1) * count).stream()
                    .collect(Collectors.joining("\n"));
            writer.write(collect);
            writer.close();
        }
    }
}

生成结果以及每个文件中的内容

img img

2. 使用HashMap

public class WordCountTest {
    public static void main(String[] args) {
        demo(
                // 创建 map 集合
                () -> new HashMap<String, Integer>(),
                // 进行计数
                (map, words) -> {
                    for (String word : words) {
                        Integer counter = map.get(word);
                        int newValue = counter == null ? 1 : counter + 1;
                        map.put(word, newValue);
                    }
                }
        );
    }
    
    private static <V> void demo(Supplier<Map<String,V>> supplier,
                                 BiConsumer<Map<String,V>, List<String>> consumer) {
        Map<String, V> counterMap = supplier.get();//获取Map集合
        List<Thread> ts = new ArrayList<>();//创建Thread 线程的ArrayList列表
        for (int i = 1; i <= 26; i++) { //启动26个线程,并发读取26个txt文件
            int idx = i;
            Thread thread = new Thread(() -> {
                List<String> words = readFromFile(idx);//读取完成一个txt文件返回200个单词
                consumer.accept(counterMap, words);//存入当前Map集合
            });
            ts.add(thread);
        }
        ts.forEach(t->t.start());//启动全部线程
        ts.forEach(t-> {
            try {
                t.join();//调用线程等待26个线程执行结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(counterMap);//26个线程全部执行完成,输出map集合
    }
    public static List<String> readFromFile(int i) {
        ArrayList<String> words = new ArrayList<>();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("tmp/"
                + i +".txt")))) {
            while(true) {
                String word = in.readLine();
                if(word == null) {
                    break;
                }
                words.add(word);
            }
            return words;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

执行结果

{a=198, b=200, c=200, d=197, e=198, f=199, g=198, h=200, i=198, j=199, k=198, l=199, m=199, n=199, o=196, p=199, q=199, r=200, s=200, t=200, u=200, v=199, w=200, x=198, y=199, z=199}

可以看到在并发统计单词个数时,没有将所有的字母统计到200。

原因分析

(map, words) -> {
    for (String word : words) {
        Integer counter = map.get(word);//读取
        int newValue = counter == null ? 1 : counter + 1;
        map.put(word, newValue);//写入
    }
}

在上述代码中,多个线程对共享资源HashMap进行读写操作,产生线程安全问题。

若一个线程map.get("a")获取到a的counter值,而其他线程在当前获取值后,操作map.put("a",newValue)修改了a的counter。则当前线程再次put会产生数据误差。

3. 解决HashMap产生的线程不安全问题

为什么使用ConcurrentHashMap而不直接使用Synchronized关键字?

直接加Synchronized属于粗粒度锁,而ConcurrentHashMap属于细粒度锁,性能更优。

如何使用ConcurrentHashMap改进

() -> new HashMap<String, Integer>()
改为
() -> new ConcurrentHashMap<String, Integer>()

仅仅是将HashMap改为ConcurrentHashMap,会保证并发安全吗?

img

依然会产生数据统计偏差。

问题分析:只将HashMap改为ConcurrentHashMap只保证内部put,get的原子性,不能保证联合起来的原子性。

修改上述代码

demo(
 () -> new ConcurrentHashMap<String, LongAdder>(),
 (map, words) -> {
 for (String word : words) {
 // 注意不能使用 putIfAbsent,此方法返回的是上一次的 value,首次调用返回 null
 map.computeIfAbsent(word, (key) -> new LongAdder()).increment();
 }
 }
);

Map.computeIfAbsent源码

default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }
 
        return v;
    }

如果缺少一个key,则计算生成一个value,将key和value放入map。整个过程被封装为原子性操作,线程安全的

同时使用LongAdder进行线程安全的自增操作。

结果验证

img

另一方案

demo(
 () -> new ConcurrentHashMap<String, Integer>(),
 (map, words) -> {
 for (String word : words) {
 // 函数式编程,无需原子变量
 map.merge(word, 1, Integer::sum);
 }
 }
);

结果验证img

标签:200,ConcurrentHashMap,word,map,单词,案例,线程,words,new
From: https://www.cnblogs.com/javaxubo/p/17498338.html

相关文章

  • 网络计划技术——供应链网络规划案例
    供应链网络是由供应商、制造商、分销商和零售商等组成的一个综合体系,用于管理和协调产品的流动和交付。它涵盖了从原材料采购到产品销售的整个过程,包括物流、库存管理、信息流等方面。一个高效的供应链网络能够实现资源的优化配置、降低成本、提高交付速度和客户满意度,从而增强企......
  • POSTGRESQL 存储过程--如何写出新版本PG的存储过程的小案例
    随着问问题的同学越来越多,公众号内部私信回答问题已经很困难了,所以建立了一个群,关于各种数据库的问题都可以,目前主要是POSTGRESQL,MYSQL,MONGODB,POLARDB,REDIS,SQLSERVER等,期待你的加入,最近在开始研究POSTGRESQL的存储过程,主要的原因有以下几个1因为要开发适合目前公司中......
  • POSTGRESQL SQL 语句案例,一场由LIMIT 1 引发的“奇怪异像”
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。最近一段工作很少优化SQL,实际上7-8年前的确有一段疯狂优化的“美好时光”。 最近一个同事提出一个问题,他的一个POSTGRESQL的SQ......
  • 智能安全应用案例:打造智能安全新体验
    目录智能安全应用案例:打造智能安全新体验随着人工智能技术的不断发展,智能安全也越来越受到人们的关注。智能安全是指借助人工智能技术对智能系统进行安全保护的一种技术。本文将介绍一种智能安全应用案例,以展示智能安全的重要性和价值。背景介绍随着大数据和云计算技术的普及,......
  • 安全监督软件中的深度学习技术:应用案例研究
    目录安全监督软件中的深度学习技术:应用案例研究随着现代网络安全问题的不断加剧,安全监督软件的需求也越来越高。安全监督软件可以用于监控网络流量、识别恶意活动、分析安全漏洞等,帮助组织提高网络安全水平。在这个背景下,深度学习技术的应用成为研究热点之一。本文将介绍深度学......
  • 应用案例分享 | 基于高精度三维机器视觉的汽车轮胎装配系统应用
    Part.1 行业背景汽车轮胎装配是汽车制造和维修过程中的关键环节,随着汽车产量的增加和市场竞争的加剧,汽车制造商对轮胎装配的自动化需求也越来越高。如今,汽车制造商们也正努力积极的提升其工艺技术水平,以应对不断增长的市场需求,希望采用更先进、更灵活、更智能的装配技术来提高汽车......
  • 北京君正应用案例:3K高清、360云台摄像机8Max评测
    现在各种视频拍摄设备都很卷,手机做到了2亿像素,行车记录都要求4K高画质了,现在也轮到云台摄像机了。家里有宠物或者宝宝保姆看管的朋友估计都会安装这种摄像机。其实这类摄像机的使用环境还是非常广的,一方面是监控家中的各种情况,另一方面还可以做店铺的监控设备,所有还是清晰......
  • 大型网站技术架构 核心原理与案例分析--阅读笔记
    第一章大型网站架构演化大型网站软件系统的特点大型网站软件系统的特点高并发、大流量高可用海量数据用户分布广法、网络情况复杂安全环境恶劣需求快速变更、发布频繁渐进式发展大型网站架构演化发展历程大型网站的技术挑战主要来自庞大的用户,高并发的访问和海量的数据,任何简单......
  • 人工智能创业投资项目案例:基于计算机视觉技术的智能物流管理系统
    目录人工智能创业投资项目案例:基于计算机视觉技术的智能物流管理系统随着人工智能的不断发展和普及,越来越多的企业开始关注和探索人工智能的应用前景,而物流管理系统作为人工智能在物流领域的应用之一,也逐渐成为了创业者和投资人的关注热点。本文将介绍一个基于计算机视觉技术的智......
  • MGR磁盘扩容案例(需要重启并切换主从的案例)
    MGR磁盘扩容案例目录MGR磁盘扩容案例前言操作流程前言通常LVM扩容是不需要重启数据库和主机的,但是因为添加了磁盘无法读取到盘,所以需要重启主机获取新增的磁盘。操作流程备库停止MGR组复制stopGROUP_REPLICATION;停库mysqladmin-uroot-p'Mg#PaS5#2020'shutdown......