JUC并发编程第十四章——线程安全集合类

1 并发集合

1.1 线程安全集合分类

a.遗留的线程安全集合

  • 遗留的线程安全集合如 Hashtable , Vector

b.使用 Collections 装饰的线程安全集合

  • 使用 Collections 装饰的线程安全集合,如:
    • Collections.synchronizedCollection
    • Collections.synchronizedList
    • Collections.synchronizedMap
    • Collections.synchronizedSet
    • Collections.synchronizedNavigableMap
    • Collections.synchronizedNavigableSet
    • Collections.synchronizedSortedMap
    • Collections.synchronizedSortedSet
  • java.util.concurrent.*

c.JUC下的安全集合: Blocking、CopyOnWrite、Concurrent

重点介绍 java.util.concurrent.* 下的线程安全集合类,可以发现它们有规律,里面包含三类关键词: Blocking、CopyOnWrite、Concurrent

  • Blocking类型 大部分实现基于锁,并提供用来阻塞的方法。该类中大部分方法都是在不满足条件时进行阻塞等待。
  • CopyOnWrite类型 之类容器修改开销相对较重。采用了一种在修改时拷贝的方式来避免多线程访问读写时的线程安全问题。适用于读多写少的情况 (写的开销相对比较大)。
  • Concurrent 类型的容器 (性能较高)
    • 优点:内部很多操作使用 cas 优化,一般可以提供较高吞吐量
    • 缺点:弱一致性
      • 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
      • 求大小弱一致性,size 操作未必是 100% 准确
      • 读取弱一致性

遍历时如果发生了修改,对于非安全容器来讲,使用 fail-fast 机制也就是让遍历立刻失败,抛出ConcurrentModifificationException,不再继续遍历


1.2 集合对比

三种集合:

  • HashMap 是线程不安全的,性能好
  • Hashtable 线程安全基于 synchronized,方法被synchronized修饰,虽然能保证线程安全,但性能太差,已经被淘汰
  • ConcurrentHashMap 保证了线程安全,综合性能较好,不止线程安全,而且效率高,性能好

注意:修饰的安全集合类,如:SynchronizedMap,本质和Hashtable类一样,都是在每个方法上加了一个sychronized关键字修饰,性能上仍然没什么提升,因此也不推荐。

集合对比:

  1. Hashtable 继承 Dictionary 类,HashMap、ConcurrentHashMap 继承 AbstractMap,均实现 Map 接口
  2. Hashtable 底层是数组 + 链表,JDK8 以后 HashMap 和 ConcurrentHashMap 底层是数组 + 链表 + 红黑树
  3. HashMap 线程非安全,Hashtable 线程安全,Hashtable 的方法都加了 synchronized 关来确保线程同步
  4. ConcurrentHashMap、Hashtable 不允许 null 值,HashMap 允许 null 值
  5. ConcurrentHashMap、HashMap 的初始容量为 16,Hashtable 初始容量为11,填充因子默认都是 0.75,两种 Map 扩容是当前容量翻倍:capacity * 2,Hashtable 扩容时是容量翻倍 + 1:capacity*2 + 1

ConcurrentHashMap数据结构

工作步骤:

  1. 初始化,使用 cas 来保证并发安全,懒惰初始化 table

  2. 树化,当 table.length < 64 时,先尝试扩容,超过 64 时,并且 bin.length > 8 时,会将链表树化,树化过程会用 synchronized 锁住链表头

    说明:锁住某个槽位的对象头,是一种很好的细粒度的加锁方式,类似 MySQL 中的行锁

  3. put,如果该 bin 尚未创建,只需要使用 cas 创建 bin;如果已经有了,锁住链表头进行后续 put 操作,元素添加至 bin 的尾部

  4. get,无锁操作仅需要保证可见性,扩容过程中 get 操作拿到的是 ForwardingNode 会让 get 操作在新 table 进行搜索

  5. 扩容,扩容时以 bin 为单位进行,需要对 bin 进行 synchronized,但这时其它竞争线程也不是无事可做,它们会帮助把其它 bin 进行扩容

  6. size,元素个数保存在 baseCount 中,并发时的个数变动保存在 CounterCell[] 当中,最后统计数量时累加


2 ConcurrentHashMap

练习demo:单词计数

生成测试数据

    static final String ALPHA = "abcedfghijklmnopqrstuvwxyz";static final String PATH = "";public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();int count = 200;int length = ALPHA.length();//将26个字母,每个字母200个,保存到list集合中for (int i = 0; i < length; i++) {char c = ALPHA.charAt(i);for (int j = 0; j < count; j++) {list.add(String.valueOf(c));}}writeToFile(list, count);//调用demo方法......//将字母写入文件中public static void writeToFile(ArrayList<String> list, int count) {//把集合打乱Collections.shuffle(list);//把集合分成26份,每份200个字母,写入文件中for (int i = 0; i < 26; i++) {try (PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(PATH + "tmp/" + (i + 1) + ".txt")))) {String collect = list.subList(i * count, (i + 1) * count).stream().collect(Collectors.joining("\n"));out.print(collect);} catch (IOException e) {}}}

模版代码,模版代码中封装了多线程读取文件的代码

//该方法有两个参数//1. 供给型参数,用来得到一个多线程之间的共同统计单词数量的map集合//2. 两个传参的消费型参数,传入两个参数,一个map一个list。该方法是:遍历list中出现的单词数量,在对应的map中把数量+1private static <V> void demo(Supplier<Map<String, V>> supplier,BiConsumer<Map<String, V>, List<String>> consumer) {Map<String, V> counterMap = supplier.get();ArrayList<Thread> ts = new ArrayList<>();//开启26个线程,每个线程统计一个文件中的单词数量,再把结果加到一个总的map中for (int i = 1; i <= 26; i++) {int idx = i;//开启线程Thread thread = new Thread(() -> {//获取该文件中的字符串集合List<String> words = readFromFile(idx);//调用BiConsumer,统计当前words中的单词数量consumer.accept(counterMap, words);});ts.add(thread);}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(counterMap);}private static <V> void demo2(Supplier<Map<String, V>> supplier,BiConsumer<Map<String, V>, List<String>> consumer) {Map<String, V> counterMap = supplier.get();ArrayList<Thread> ts = new ArrayList<>();CountDownLatch countDownLatch = new CountDownLatch(26);//开启26个线程,每个线程统计一个文件中的单词数量,再把结果加到一个总的map中for (int i = 1; i <= 26; i++) {int idx = i;//开启线程Thread thread = new Thread(() -> {try {//获取该文件中的字符串集合List<String> words = readFromFile(idx);//调用BiConsumer,统计当前words中的单词数量consumer.accept(counterMap, words);} finally {countDownLatch.countDown();}});thread.start();ts.add(thread);}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(counterMap);}//返回文件中所有的字符串public static List<String> readFromFile(int i) {ArrayList<String> list = new ArrayList<>();try {BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(PATH + "tmp/" + i + ".txt")));while (true) {String word = in.readLine();if (word == null) {break;}list.add(word);}} catch (Exception e) {e.printStackTrace();}return list;}

说明一下:这里demo和demo2方法是一样的,只是demo2我想使用一下CountDownLatch

正确输出结果应该为:

正确结果输出应该是每个单词出现 200 次
{a=200, b=200, c=200, d=200, e=200, f=200, g=200, h=200, i=200, j=200, k=200, l=200, m=200, n=200, o=200, p=200, q=200, r=200, s=200, t=200, u=200, v=200, w=200, x=200, y=200, z=200} 

错误调用demo方法

demo(// 创建 map 集合// 创建 ConcurrentHashMap 对不对?() -> new HashMap<String, Integer>(),// 进行计数(map, words) -> {for (String word : words) {//这里的getter和setter无法保证原子性Integer counter = map.get(word);int newValue = counter == null ? 1 : counter + 1;map.put(word, newValue);}}
);

运行结果:

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

说明:可以看到运行结果是有问题的。原因是put新的value的时候,那三段代码无法保证原子性。


解决方案1

        //调用demo方法demo(() -> new ConcurrentHashMap<String, Integer>(), (map, words) -> {for (String word : words) {synchronized (map) {Integer counter = map.get(word);map.put(word, counter == null ? 1 : counter + 1);}}});

说明:这里可以通过加synchronized锁,保证对于map中value修改的时候的原子性。但问题是锁了整个map性能下降。

解决方案2

     demo2(() -> new ConcurrentHashMap<String, LongAdder>(),(map, words) -> {for (String word : words) {// 注意不能使用 putIfAbsent,此方法返回的是上一次的 value,首次调用返回 null// 解释一下这段代码:首先computeIfAbsent是判断当前word是否为null,// 如果为null就创建一个新的LongAdder(初始值为 0)// 如果不为null,就根据当前key(就是word)从map中获取对应的value(LongAdder)LongAdder value = map.computeIfAbsent(word, (key) -> {return new LongAdder();});//即使这里设置key-value和value自增不是原子性操作,LongAdder的CAS会获取到最新的值再进行加一的,这一点可以放心// 不需要加锁,性能更好value.increment();//再把这个累加器+1}});

使用computeIfAbsent和LongAdder中的CAS原理,避免锁整个map集合。

解决方案3

    demo(() -> new ConcurrentHashMap<String, Integer>(), (map, words) -> {for (String word : words) {//这里根据word从map中找value,如果value为null,初始值就为0//反之,value就是map中根据key获取的结果//最后把a和b(1)相加map.merge(word, 1, (a, b) -> {return a + b;});}});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/354005.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【FreeRTOS】估算栈的大小

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》 目录 估算栈的大小回顾简介计算说明估计函数用到的栈有多大合计 估算栈的大小 回顾 上一篇文章链接&#xff1a;http://t.csdnimg.cn/Cc8b4 传送门: 上一篇文章 上一篇文章创建的三个任务 /* 创建任务&#xff1a;声 *…

一个新的剪辑拼接图片和视频类APP在测试阶段需要测试内容,以iPhone APP为例:

1.UI参照原型图和设计稿 如有改动&#xff0c;需及时沟通 2.iPad转屏、不同iPhone和iPad机型测试 3.黑夜白天模式 2.各功能模块流程需要测试跑通 3.订阅支付模块 a. UI设计是否和设计稿一致 b.涉及订阅的位置都要测试 c.免费试用是否显示&#xff1b;试用结束后&#xff0c…

【Gitlab】访问默认PostgreSQL数据库

本地访问PostgreSQL gitlab有可以直接访问内部PostgreSQL的命令 sudo gitlab-rails dbconsole # 或者 sudo gitlab-psql -d gitlabhq_production效果截图 常用SQL # 查看用户状态 select id,name,email,state,last_sign_in_at,updated_at,last_credential_check_at,last_act…

C语言学习之路(黑马)

文章目录 环境搭建HelloWorld代码编写代码分析执行流程 核心语法注释单行注释多行注释注释示例 关键字常量变量计算机进制数据类型标识符键盘录入 运算符算术运算符比较运算符赋值运算符自增减运算符逻辑运算符三元运算符逗号运算符运算符的优先级 流程控制语句顺序结构分支结构…

配置Linux DNS服务器作为自己的windows 的 DNS服务器和 配置遇到的问题

安装DNS 库 和 DNS工具&#xff1a; # bind 是用于创建 dns服务的&#xff0c; bind-utils是用于测试DNS服务的工具 yum -y install bind bind-utils配置主配置文件&#xff1a; # 下载好后就已经有DNS服务&#xff0c;但是需要你自己去配置DNS服务信息# 配置主配置文件 [rootl…

ChatGPT 提示词技巧一本速通

目录 一、基本术语 二、提示词设计的基本原则 三、书写技巧 2.1 赋予角色 2.2 使用分隔符 2.2 结构化输出 2.3 指定步骤 2.4 提供示例 2.5 指定长度 2.6 使用或引用参考文本 2.7 提示模型进行自我判断 2.8 思考问题的解决过程 ​编辑 2.10 询问是否有遗漏 2.11 …

Spring源码-xxxAware实现类和BeanPostProcessor接口调用过程

xxxAware实现类作用 以ApplicationContextAware接口为例 ApplicationContextAware的作用是可以方便获取Spring容器ApplicationContext&#xff0c;从而可以获取容器内的Bean package org.springframework.context;import org.springframework.beans.BeansException; import or…

RK3588 Android12音频驱动分析全网最全

最近没有搞音频相关的了&#xff0c;在搞BMS, 把之前的经验总结一下。 一、先看一下Android 12音频总架构 从这张图可以看到音频数据流一共经过了3个用户空间层的进程&#xff0c;然后才流到kernel驱动层。Android版本越高&#xff0c;通用性越高&#xff0c;耦合性越低&#…

hdfs文件系统增删查原理

目录 1、hdfs读取文件原理 1.1、读取流程图解 1.2、架构层面读取流程详解 1.3、源码层面读取流程详解 2、hdfs写入文件原理 2.1、写入流程图解 2.2、架构层面写入流程 2.3、源码层面写入流程 3、hdfs删除文件原理 3.1、删除文件图解 3.2、架构层面删除流程 3.3、源码…

【Java】已解决java.lang.UnsupportedOperationException异常

文章目录 问题背景可能出错的原因错误代码示例正确代码示例注意事项 已解决java.lang.UnsupportedOperationException异常 在Java编程中&#xff0c;java.lang.UnsupportedOperationException是一个运行时异常&#xff0c;通常表示尝试执行一个不支持的操作。这种异常经常发生…

Word2Vec基本实践

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

MATLAB入门知识

目录 原教程链接&#xff1a;数学建模清风老师《MATLAB教程新手入门篇》https://www.bilibili.com/video/BV1dN4y1Q7Kt/ 前言 历史记录 脚本文件&#xff08;.m&#xff09; Matlab帮助系统 注释 ans pi inf无穷大 -inf负无穷大 i j虚数单位 eps浮点相对精度 0/&a…

【AI】通义千问使用指南:让你快速上手,成为问题解决高手!

大家好&#xff0c;我是木头左。 近日&#xff0c;继文心一言和讯飞星火之后&#xff0c;阿里虽迟但到&#xff0c;直接宣布开源两款“通义千问”大模型。作为国内首个开源且可商用的人工智能大模型&#xff0c;这会给我们带来哪些变化呢&#xff1f; 如何申请阿里通义千问&am…

JupyterLab使用指南(六):JupyterLab的 Widget 控件

1. 什么是 Widget 控件 JupyterLab 中的 Widget 控件是一种交互式的小部件&#xff0c;可以用于创建动态的、响应用户输入的界面。通过使用 ipywidgets 库&#xff0c;用户可以在 Jupyter notebook 中创建滑块、按钮、文本框、选择器等控件&#xff0c;从而实现数据的交互式展…

springboot集成积木报表,怎么将平台用户信息传递到积木报表

springboot集成积木报表后怎么将平台用户信息传递到积木报表 起因是因为需要研究在积木报表做数据筛选的时候需要拿到系统当前登录用户信息做筛选新的模块 起因是因为需要研究在积木报表做数据筛选的时候需要拿到系统当前登录用户信息做筛选 官网有详细介绍怎么集成进去的&…

力扣每日一题 6/19 排序+动态规划

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 2713.矩阵中严格递增的单元格数【困难】 题目&#xff1a; 给你一个下标从…

爆赞!GitHub首本Python开发实战背记手册,标星果然百万名不虚传

Python (发音:[ paiθ(ə) n; (US) paiθɔn ] n. 蟒蛇&#xff0c;巨蛇 )&#xff0c;是一种面向对象的解释性的计算机程序设计语言&#xff0c;也是一种功能强大而完善的通用型语言&#xff0c;已经具有十多年的发展历史&#xff0c;成熟且稳定。Python 具有脚本语言中最丰富…

无问芯穹Qllm-Eval:制作多模型、多参数、多维度的量化方案

前言 近年来&#xff0c;大语言模型&#xff08;Large Models, LLMs&#xff09;受到学术界和工业界的广泛关注&#xff0c;得益于其在各种语言生成任务上的出色表现&#xff0c;大语言模型推动了各种人工智能应用&#xff08;例如ChatGPT、Copilot等&#xff09;的发展。然而…

list集合自定义排序

一、基本类型排序 1.list中只有数字或字符串 //升序排序 List<T> ,T为数字或字符串 Collections.sort(list); //降序排序 Collections.sort(list,Collections.reverseOrder());2.list中为对象 基于jdk.18 import lombok.Data;Data public class User {private int i…

Android网络性能监控方案 android线上性能监测

1 Handler消息机制 这里我不会完整的从Handler源码来分析Android的消息体系&#xff0c;而是从Handler自身的特性引申出线上卡顿监控的策略方案。 1.1 方案确认 首先当我们启动一个App的时候&#xff0c;是由AMS通知zygote进程fork出主进程&#xff0c;其中主进程的入口就是Ac…