Java多线程环境下使用的集合类

news2025/1/10 6:08:00

Java标准库中大部分集合类都是线程不安全的, 多线程环境下使用同一个集合类对象, 很可能会出问题; 只有少部分是线程安全的, 比如: Vector, Stack, HashTable这些, 关键方法都会带有synchronized, 但一般是不推荐使用这几个类的.

一. 多线程环境下使用ArrayList

ArrayList在多线程中是线程不安全的, 使用时要保证线程安全的话有如下几种方式:

  1. 涉及线程安全问题的代码中, 自己使用synchronized或者ReentrantLock进行加锁.
  2. 使用标准库里面的操作: Collections.synchronizedList(new ArrayList); synchronizedList中的关键操作上都带有synchronized的.
  3. 使用基于写实拷贝实现的CopyOnWriteArrayList.

所谓写实拷贝就是, 如果针对该ArrayList进行读操作, 不会做任何额外的工作, 因为只有只有读操作的话是不涉及线程安全问题的;

如果进行写操作则拷贝一份新的ArrayList, 然后针对新的ArrayList进行修改, 如果在写过程中有读操作, 那么就去读旧ArrayList, 当我们新的ArrayList写操作完成之后, 就让新的替换旧的ArrayList(本质上就是一个引用的赋值, 是原子的).

很明显, 这里写时拷贝的方案, 优点是在读多写少的场景下, 性能很高, 不需要加锁竞争; 缺点是这种方案占用内存较多, 要求这个ArrayList不能太大, 而且新写的数据不能被第一时间读取到.

🍂写时拷贝的应用:

这种写时拷贝的思路可以应用在服务器的 “热加载” (reload) 这样的功能上.

在服务器程序中, 包含有很多的子功能, 有的功能想要使用, 有的不想要使用, 有的希望功能应用不同, 所以可以使用一系列的 “开关选项” 来控制当前这个程序的工作状态, 这里就涉及到服务器程序配置文件的修改, 修改配置后可能就需要重启服务器才能生效, 但是重启操作可能成本比较高.

为什么说成本比较高呢?

假设一个服务器重启需要花5min(往小了说的), 如果有20台这样的服务器, 总的重启时间就得100min, 注意这20台服务器是不能一起重启的, 一起重启就意味着在这5min中所有的服务器都不工作了, 服务就中断了, 用户发起的请求就没有了任何的响应, 这个状况就是比较严重的事故了.

而使用 “热加载” 这样功能就可以不重启服务器实现配置的更新, 可以利用写时拷贝的思路, 新的配置放到新的对象中, 加载过程中, 请求仍然基于旧配置进行工作, 当新的对象加载完毕, 就使用新对象替代旧对象(替换完成之后,旧的对象就可以释放了).

二. 多线程环境使用队列

多线程环境下通常使用的是阻塞队列:

  1. ArrayBlockingQueue 基于数组实现的阻塞队列.
  2. LinkedBlockingQueue 基于链表实现的阻塞队列.
  3. PriorityBlockingQueue 基于堆实现的带优先级的阻塞队列.
  4. TransferQueue 最多只包含一个元素的阻塞队列.

三. 多线程环境下使用哈希表

HashMap本身是线程不安全的, 将HashMap中的重要方法使用synchornized加锁后, 就得到了Hashtable类, Hashtable是线程安全的, 但相比于Hashtable更推荐使用的是ConcurrentHashMap.

那么下面就来分析一下, ConcurrentHashMap相较于Hashtable进行了哪些优化.

🎯1. 最大的优化: ConcurrentHashMap相较于Hashtable大大缩小了锁冲突的概率, 将一把大锁换为多把小锁.

首先看线程不安全的HashMap, 假设表中如下图, 这里元素1, 2是在同一个链表上, 如果线程A修改(增/删/改)元素1, 线程B修改(增/删/改)元素2, 这里是存在线程安全问题的, 1和2位置这是两个相邻的节点, 如果此时并发的插入/删除, 就需要修改这两节点next的指向, 所以这个情况要解决是需要加锁的.
再来看如果线程A修改元素3, 线程B修改元素4, 这里是没有线程安全问题的, 3和4位置的节点是位于两个不同的链表中的, 这个情况就相当于多个线程并发去修改不同的变量, 是不存在线程安全问题的, 随之这里也就是不需要加锁了.

img

上面说到的HashMap是有线程安全问题的, 而针对上面的场景使用Hashtable是可以解决线程安全问题的, 但Hashtable中不太必要的是它是直接在方法上加synchronized, 等于是是给this加锁, 这把大锁就导致了我们只要操作哈希表上的任意元素, 都会加锁, 也就都可能会发生锁冲突.

img

但是实际上, 基于哈希表的结构特点, 有些元素在进行并发操作的时候, 是不会产生线程安全问题的, 也就没必要去加锁控制了, 就如上面分析过的3和4位置的元素.

这就是不推荐使用Hashtable的主要原因, 这里锁冲突概率太大了, 就势必会造成一些并发效率低下的问题.

img

ConcurrentHashMap的做法是每个链表有各自的锁, 就不是像Hashtable一样大家共用一个锁了, 具体来说, 就是使用每个链表的头结点, 作为锁对象, 这样只有并发操作同一个链表中的元素才会有锁竞争, 大大降低了锁冲突的概率, 相较于Hashtable并发效率上也会提升不少.

相较于Hashtable, 这里就是把锁的粒度变小了, 不同线程操作1和2时, 是针对同一把锁进行加锁, 会产生锁竞争, 保证了线程安全; 而不同线程操作3和4时, 是针对不同的锁进行加锁, 所以不会产生锁竞争.

img

上图中的情况, 是针对JDK1.8及其以后的情况, 而JDK1.8之前, ConcurrentHashMap使用的是 “分段锁”, 分段锁本质上也是缩小锁的范围从而降低锁冲突的概率, 但是这种做法不够彻底, 一方面锁的粒度切分的还不够细, 另一方面代码实现也更繁琐.

img

🎯2. ConcurrentHashMap做了一个激进的操作, 只针对写操作加锁, 而针对读操作不加锁, 这里读和读之间没有冲突, 写和写之间有冲突, 而读和写之间也没有冲突是为什么呢?

在很多场景下, 读写之间不加锁控制,可能会读到一个写了一半的结果, 如果写操作不是原子的, 此时读就可能会读到写了一半的数据, 相当于脏读了, 而这里的写操作使用了volatile来保证我们每次都是从内存读取的结果并且写操作加锁保证了写操作是原子的, 这样就没有上述说到的问题了.

🎯3. ConcurrentHashMap内部充分利用CAS特性, 来减少加锁的操作, 比如通过CAS来维护size属性(元素个数).

🎯4. 针对扩容操作, 采取了"化整为零"的策略.

HashMapHashtable中的扩容, 是直接创建一个空间更大新数组, 然后将旧的数组上的每个元素搬到新数组上(删除节点+插入节点), 当我们某一次进行put时, 元素个数达到负载因子设定的值, 就会触发扩容操作, 此时如果哈希表中的元素特别多, 扩容操作就会比较耗时, 也就是某次put比平时的put卡很多倍.

而在ConcurrentHashMap中采取扩容方式是每次只搬运─小部分元素, 具体来说就是, 扩容时创建一个新的数组, 旧的数组也会保留下来, 之后的每次put操作直接往新数组上添加, 同时搬运一部分旧的元素到新数组上, 当进行get操作时, 新旧数组都进行查询, 当进行remove操作时, 元素在哪个数组上正常进行删除操作即可, 这样经过一段时间之后, 当所有元素都搬运到了性数组上, 然后再释放旧数组即可.

🍂总结:

  • HashMap: 线程不安全, key 允许为 null.
  • Hashtable: 线程安全, 使用 synchronized 锁 Hashtable 对象, 效率较低, key 不允许为 null.
  • ConcurrentHashMap: 线程安全, 使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用 CAS 机制, 优化了扩容方式, key 不允许为 null.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅析设计模式4——模板方法模式

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型…

OAuth2简介

目录一、 什么是OAuth2二、OAuth2中的角色三、认证流程四、生活中的Oauth2思维五、令牌的特点六、OAuth2授权方式1、授权码2、隐藏方式3、密码方式4、凭证方式一、 什么是OAuth2 OAuth2.0是目前使用非常广泛的授权机制,用于授权第三方应用获取用户的数据。 举例说明…

使用Vuex的个人理解

一、逻辑原理 要使用 Vuex 进行集中管理数据(状态),按照 Vuex 分模块的设 计思想,先在 src 下创建 store 文件夹,然后创建一个根级别的 index.js,作为组装模块并导出 store 地方(store 对象是 …

xss.haozi.me通关教程

10.xss.haozi.me通关教程 0x00 首先整体浏览网站 分别是xss注入点&#xff0c;注入后的HTML代码以及网页源码 构造常规payload&#xff1a; <script>alert(1)</script> 成功通关 0x01 看到注入点是在标签中, 所以用上一题的方法是不会被解析的, 故需要去构造…

MySQL_索引

索引概述 简介 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff…

OKR之剑·实战篇04:OKR执行过程优化的那些关键事

作者&#xff1a;vivo 互联网平台产品研发团队 本文是《OKR 之剑》系列之实战第 4 篇——OKR执行过程不是一成不变的&#xff0c;团队和个人在执行中不断优化执行的具体行动&#xff0c;保障OKR的高效执行。 前言 “言治骨角者&#xff0c;既切之而复磋之&#xff1b;治玉石者…

vue axios post 请求415、400、500、404报错时

vue axios post 请求415 415错误的解释&#xff0c;服务器无法处理请求附带的媒体格式。 解决方式&#xff1a; 后端参数使用了RequestBody注解进行绑定&#xff0c;用了这个注解进行数据绑定&#xff0c;只能接受数据类型为 Content-Type类型为application/json 1.后台修改&am…

notepad++ 中安装NppExec插件

一、何为NppExec简单的说&#xff0c;这个插件可以让用户在NPP中直接运行一些命令和程序&#xff0c;而不用启动这些命令和程序对应的实际工具或编译器。1. NppExec是...NppExec是介于Notepad和外部工具/编译器之间的一个中间件。它允许用户在NPP中直接运行这些工具/编译器。Np…

智能管理PoE交换机

随着网络化信息化的快速发展&#xff0c;以太网供电&#xff08;PoE&#xff09;的优势逐渐被大家所熟知。只需要一根网线&#xff0c;PoE在给网络设备供电的同时还能传输数据&#xff0c;免布线&#xff0c;节省成本和空间&#xff0c;设备可随意移动&#xff0c;系统部署灵活…

TCP/IP网络编程——域名及网络地址

完整版文章请参考&#xff1a; TCP/IP网络编程完整版文章 文章目录第 8 章 域名及网络地址8.1 域名系统8.1.1 什么是域名8.1.2 DNS 服务器8.2 IP地址和域名之间的转换8.2.1 程序中有必要使用域名吗&#xff1f;8.2.2 利用域名获取IP地址第 8 章 域名及网络地址 8.1 域名系统 …

重建大师可以实现识别地表树木吗?对数据格式有什么要求?

目前重建大师5.0具备自动单体化与切割单体化功能&#xff0c;在三维重建的过程中就可自动识别地表树木、建筑。 数据格式的话&#xff0c;目前是需要重建大师生产的倾斜模型数据&#xff0c;其他软件生产的模型暂不支持。重建大师目前并不是直接对模型数据进行深度学习识别&am…

Node.js包和模块的区别在哪儿

在Node.js 中&#xff0c;会将某个独立的功能封装起来&#xff0c;用于发布、更新、依赖管理和进行版本控制。Nodejs 根据CommonJS规范实现了包机制&#xff0c;开发了NPM包管理工具&#xff0c;用来解决包的发布和获取需求。 Node.js的包和模块并没有本质的不同&#xff0c;包…

详解linux中网络的几种模式:NAT,网桥,以及静态IP的配置和主机名

NAT模式 NAT就是网络地址转换&#xff0c;虚拟机和主机构建一个专有网络&#xff0c;通过NAT进行设备IP的转换&#xff0c;虚拟机通过共享主机的IP访问外界网络&#xff0c;但外部网络无法访问虚拟机。构建出的子网一般是WNET8. 网桥模式 也叫桥接模式&#xff0c;虚拟机直接…

Android发送广播时报错:Sending non-protected broadcast xxxxxxx from system xxxxxxxxxx

带android:sharedUserId“android.uid.system” 发送广播时&#xff0c;会出现 Sending non-protected broadcast 异常提醒&#xff1b; 原因&#xff1a; Ams在发送广播时&#xff0c;对于systemApp(系统应用)&#xff0c;会要求发送广播必须是声明在frameworks\base\core\re…

【实战项目】Django-Vue007---Redis、Python操作redis之普通连接和连接池、redis操作各种数据、django中使用redis

回顾 1、用户登录注册相关5个接口 多方式登录接口 手机号是否存在接口 发送验证码接口 验证码登录接口 验证码注册接口2、可以写验证码注册登录接口 如果手机号存在&#xff0c;直接登录成功 如果手机号不存在&#xff0c;直接创建用户&#xff0c;并且成功登录&#xff08;…

【计算机基础学科】操作系统学习笔记(考研王道参考书)

操作系统目录 文章目录操作系统目录一、操作系统概述1.1 操作系统基本概念1.1.1 操作系统的概念、功能1.1.2 操作系统的特性1.1.3 操作系统的发展与分类1.1.4 操作系统的运行机制1.2 内核态与用户态1.3 中断、异常1.4 系统调用~~1.5 计算机的层次结构~~~~1.6 操作系统引导&…

【数学建模】常用算法-主成分分析PCA的Python实现

1前言 本文主要讲解主成分分析析法&#xff08;PCA&#xff09;的python实现&#xff0c;后续会跟进实例分析 2 原理-代码实现 2.1 实现步骤 主成分分析PCA是一种应用广泛的和降维方法&#xff0c;对其实现做以下归纳 2.2 代码实现 导入包 import numpy as np定义计算协…

VBA提高篇_12 文本文件逐行读取,输入输出操作自如

文章目录一、VBA打开文本文件的4步操作二、文本输入Excel示例三、 Excel输出文本示例3.1 Open For Output3.2 Open For Append3.3 Print 打印文件3.4 工作簿 循环工作表 循环输出文件四 、先输入后输出 Demo实战演练一、VBA打开文本文件的4步操作 打开文本文件 找到指定文件并调…

看这篇就够了——ubuntu扩展屏幕及装显卡驱动后黑屏问题

1.问题说明首先说明为什么外接屏幕需要装显卡驱动&#xff0c;显卡由GPU和显存构成&#xff0c;又称为显示适配器&#xff0c;与数据的输出有密切关系。在ubuntu系统中&#xff0c;外接显示器是用的独立显卡驱动&#xff0c;而内置屏幕用的是集显驱动,也正是因为调用显卡的不同…

Day01-数据分析图鉴

文章目录Day01-数据分析图鉴一、 数据分析是什么&#xff1f;二、为什么数据分析这么火&#xff1f;三、数据分析的行业现状如何&#xff1f;四、对工作经验要求高吗&#xff1f;五、工资情况六、学完数据分析的效果&#xff1f;1、Python可视化2、TableauDay01-数据分析图鉴 …