多线程——线程安全的集合类

目录

·前言

一、多线程环境使用 ArrayList

1.进行加锁

2.使用 SynchronizedList 类

3.使用 CopyOnWriteArrayList 类

二、多线程环境使用队列

1.进行加锁

2.使用阻塞队列

三、多线程环境使用哈希表

1.Hashtable

2.ConcurrentHashMap

(1)缩小锁粒度

(2)使用 CAS 机制

(3)优化扩容操作

3.Hashtable、HashMap 与 ConcurrentHashMap 的区别

·结尾


·前言

        介绍了这么多关于多线程相关的知识后,在我们 Java 中哪些集合类在使用的时候是线程安全的呢?其实,我们了解的集合类大部分都是线程不安全的,像 HashMap、ArrayList……,那么本篇文章就来介绍一下哪些集合类是线程安全的,它们又是如何保证线程安全的。

一、多线程环境使用 ArrayList

1.进行加锁

        为了保证在多线程环境下,对 ArrayList 进行操作是线程安全的,我们可以在调用 ArrayList 的相关方法时都进行加锁操作,使用 synchronized 或者使用 ReentrantLock,这样就可以保证线程的安全。

2.使用 SynchronizedList 类

        使用这种方式创建 ArrayList 方式如下代码所示:

List list = Collections.synchronizedList(new ArrayList());

        这种方式可以认为是给 ArrayList 套了一个壳,原本 ArrayList 各种操作都是不带锁的,通过上述套壳操作后,得到了新的对象,在新的对象中,里面的方法就都是带有锁的。

        使用这种方式可以把 List 所以的子类都转成线程安全的类,关于这种方式的详细用法与介绍可以参考一下:Collections.synchronizedList使用 - hongdada - 博客园 这篇文章,里面的讲解十分详细,我在这里就不展开进行介绍啦。

3.使用 CopyOnWriteArrayList 类

        CopyOnWriteArrayList 类保证线程安全的做法是用到了写时拷贝,在多线程环境下使用 ArrayList 遇见的线程安全问题大多都是涉及到多个线程对同一个 ArrayList 进行修改操作,使用 CopyOnWriteArrayList 在面对多个线程修改时,进行的操作就是把整个顺序表复制一份,修改新的顺序表中的内容,修改完成后再修改引用的指向,使引用指向新的顺序表,用这种方式来保证线程安全,这个过程如下图所示:

        最后修改引用指向的操作是原子的所以不加锁也不会有问题。

        上述的这种操作也是存在一定的局限性的,比如出现一下两种情况:

  1. 对 List 涉及到频繁的修改操作;
  2. 顺序表非常大; 

        此时使用 CopyOnWriteArrayList ,写时拷贝就会产生很大的开销,如果这种开销比加锁的开销还要大,那就得不偿失了。

二、多线程环境使用队列

1.进行加锁

        想要在多线程环境中使用队列保证线程安全,那么我们可以在调用队列相关方法时都进行加锁的操作,使用 synchronized 或者使用 ReentrantLock,这样就可以保证线程的安全。

2.使用阻塞队列

        使用 BlockingQueue 实现的阻塞队列,要注意,阻塞队列不仅有阻塞的作用,还有保证线程安全的作用,关于阻塞队列的介绍可以参考我前面的文章:多线程——阻塞队列_php 队列阻塞问题-CSDN博客 这里我对阻塞队列进行了详细的介绍,已经实现一个简单的阻塞队列。

三、多线程环境使用哈希表

        在多线程中使用 HashMap 本身不是线程安全的,想要在多线程环境下使用哈希表就可以使用:Hashtable 或者 ConcurrentHashMap。

1.Hashtable

        Hashtable 这里保证线程安全的方式很简单,如下图所示:

        进入 Hashtable 源码可以发现,Hashtable 这里保证线程安全的方式就是把关键的方法都加上 synchronized 关键字,前面介绍过,对方法加 synchronized 进行修饰,等同于直接针对 Hashtable 对象本身进行加锁,这时就会出现下图的情况:        上图两个线程是想针对不同的链表进行修改操作,可是由于 Hashtable 中 synchronized 直接对 Hashtable 对象本身进行了加锁,所以即使是修改不同的链表也会出现锁冲突。

        我们可以仔细观察一下上图,如果是修改两个不同链表上的元素就不会涉及到线程安全的问题,这属于修改不同变量,但是如果是修改同一个链表上的元素就可能涉及到线程安全问题,此时针对不同链表的操作再进行加锁就产生了多余的锁冲突了。

        下面我来总结一下 Hashtable 的缺点,有以下几条:

  1. 如果多个线程访问同一个 Hashtable 就会直接发送锁冲突,这时就会产生一些多余的锁冲突,造成代码在多线程执行中效率低下;
  2. size 方法也是使用 synchronized 进行修饰的,操作起来就会比较慢;
  3. Hashtable 一旦涉及到扩容操作,就由该线程完成整个扩容操作,这个过程中涉及到大量的元素拷贝,效率会非常低。 

2.ConcurrentHashMap

        ConcurrentHashMap 相比与 Hashtable 做出了一系列的改进和优化,下面我来介绍一下,ConcurrentHashMap 都做了些什么优化。

(1)缩小锁粒度

        为了优化 Hashtable 中对不同链表操作出现的多余锁冲突,在 ConcurrentHashMap 中的加锁方式是对每条链表都分配一个单独的锁,如下图所示:

        此时,t1 线程与 t2 线程在修改不同链表的时候就不会出现锁冲突,并且,这里虽然是对每条链表都分配了把锁,但是并没有产生更多的空间代价,这是因为 Java 中任何一个对象都可以作为锁对象,在哈希表中,本身就要有数组,数组中每个元素都是已经存在的(每个链表的头节点),此时,只要使用数组元素(链表头节点)作为加锁的对象即可完成对每条链表都分配锁的操作。

        在 Java 1.7 及以前,ConcurrentHashMap 是通过“分段锁”来实现的,就是给若干个链表分配一把锁,这种设定实现更复杂,效率也不高,还会引入额外的空间开销,所以从  Java 1.8 开始,ConcurrentHashMap 就设定成每个链表一把锁了。

(2)使用 CAS 机制

        在 ConcurrentHashMap 中充分使用了 CAS 机制进行原子操作,减少了部分加锁,比如:在针对整个哈希表元素的个数维护。

(3)优化扩容操作

        扩容对于哈希表来说永远都是一个重量级的操作,在 HashMap 中有一个负载因子,它用来描述每个链表上平均有多少元素,为了保证哈希表的查找效率,每个链表上的元素个数不应该太长,如果太长就会涉及到以下两种解决方案:

  1. 面对个别链表长度过长,会将该链表变成树状结构;
  2. 如果负载因子到达规定阈值,就会进行扩容操作。

        所谓的扩容操作就是创建一个更大的数组,把旧的哈希表中的元素都搬运(插入/删除)到新的数组上,如果哈希表本身元素有很多,这里的扩容操作就会消耗很长时间。

        那么 ConcurrentHashMap 是如何针对扩容操作进行的优化呢?这里使用的是化整为零的思路,以往的 HashMap 或者 Hashtable 面对扩容操作都是在某一次插入元素操作中将整体一次性完成扩容,而 ConcurrentHashMap 在面对扩容操作时是每次操作都只搬运一部分元素,这样确保每次操作消耗的时间都不会很长,就避免了出现很卡的情况了。

        在 ConcurrentHashMap 进行扩容的过程中会同时存两份哈希表,一份是旧的,一份是新的,此时进行的各操作流程为:

  • 插入操作:直接往新的哈希表中插;
  • 删除操作:新的哈希表与旧的哈希表中都直接删除;
  • 查找操作:在新的哈希表与旧的哈希表中都进行查询。 

        最后扩容完成后,把旧的哈希表给删除。 

3.Hashtable、HashMap 与 ConcurrentHashMap 的区别

  1. HashMap:线程不安全,key 允许为 null;
  2. Hashtable:线程安全,使用 synchronized 锁 Hashtable 对象,增加多余的所冲突,效率较低,key 不允许为 null;
  3. ConcurrentHashMap:线程安全,使用 synchronized 锁每个链表的头节点,锁冲突的概率降低,充分利用 CAS 机制,优化了扩容的方式,key 不允许为 null。

·结尾

        文章到此就要结束了,本篇文章介绍了一些线程安全的集合类,并且介绍了它们是如何保证线程安全的,希望看完本篇文章,让你在多线程编程的过程中会多注意使用的类是否存在线程安全问题,并且理解上述线程安全的集合类为保证线程安全所进行操作的思路,这会对我们在多线程环境下如何使用线程不安全的类也能写出线程安全的代码有所帮助,如果本篇文章对你有所帮助,希望能得到你的支持,如果对文章有什么不理解的地方欢迎在评论区进行留言,那么我们就下一篇文章再见咯~~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue文件转AST,并恢复成vue文件(适用于antdv版本升级)

vue文件转AST,并恢复成vue文件---antdvV3升级V4 vue文件转AST,重新转回原文件过程如何获取项目路径读取项目文件,判断文件类型分别获取vue文件 template js(vue2和vue3)处理vue 文件template部分处理vue script部分uti…

【线下+线上会议|国内外双会场】2024年第四届数字化社会与智能系统国际学术会议(DSInS 2024)-悉尼/郑州双会场

2024年第四届数字化社会与智能系统国际学术会议(DSInS 2024)-悉尼/郑州双会场 2024 4th International Conference on Digital Society and Intelligent Systems 会议官网:www.dsins.org 2024 4th International Conference on Digital Soc…

龙迅#LT89101 适用于 MIPI DSI/CSI摄像头和 LVDS 中继信号延长功能,分辨率可支持 1080P@60HZ!

1. 描述 Lontium LT89101 是一款高性能 MIPI DSI/CSI-2 和 LVDS 中继器,用于汽车系统应用的移动显示或摄像头信号整形。 LT89101采用先进的 CMOS 工艺制造,并采用小外形 7.5mm x 7.5mm QFN64 封装。该封装符合 RoHS 标准,额定工作温度范围为 …

MySQL8.0.40编译安装

近期MySQL发布了8.0.40版本,与之前的版本相比,部分依赖包发生了变化,因此重新编译一版,也便于大家参考。 1. 下载源码 选择对应的版本、选择源码、操作系统 如果没有登录或者没有MySQL官网账号,可以选择只下载 2. 进…

element 按钮变形 el-button样式异常

什么都没动,element UI的按钮变形了,莫名其妙,连官网的也变形了,换了其它浏览器又正常, 难道这是element UI的问题?NO,是浏览器的插件影响到了!去扩展插件里面一个个关闭扩展&#x…

MySql中的锁的分类

锁的分类 MySQL锁可以按模式分类为:乐观锁与悲观锁。按粒度分可以分为全局锁、表级锁、页级锁、行级锁。按属性可以分为:共享锁、排它锁。按状态分为:意向共享锁、意向排它锁。按算法分为:间隙锁、临键锁、记录锁。 二、全局锁、表…

ClickHouse与各种组件的关系

ClickHouse和其他组件关系如下: Flink支持ClickHouse Sink支持Hive/SparkSQL数据批量导入ClickHouseHetuEngine支持ClickHouse数据源常用第三方工具如DBeaver支持ClickHouse对接ClickHouse依赖ZooKeeper实现了分布式DDL执行以及ReplicatedMergeTree表主备节点之间的…

多线程—— JUC 的常见类

目录 前言 一、Callable 接口 1.Callable 介绍 2.代码示例 3.创建线程的方式 二、ReentrantLock 类 1.ReentrantLock 介绍 2.代码示例 3.与 synchronized 的区别 三、信号量 Semaphore 类 1.Semaphore 介绍 2.代码示例 3.保证线程安全的方式 四、CountDownLatch …

二、Spring的执行流程

文章目录 1. spring的初始化过程1.1 ClassPathXmlApplicationContext的构造方法1.2 refresh方法(核心流程)1.2.1 prepareRefresh() 方法1.2.2 obtainFreshBeanFactory() 方法1.2.3 prepareBeanFactory() 方法1.2.4 invokeBeanFactoryPostProcessors() 方…

(linux驱动学习 - 12). IIC 驱动实验

目录 一.IIC 总线驱动相关结构体与函数 1.i2c_adapter 结构体 2.i2c_algorithm 结构体 3.向系统注册设置好的 i2c_adapter 结构体 - i2c_add_adapter 4.向系统注册设置好的 i2c_adapter 结构体 - i2c_add_numbered_adapter 5.删除 I2C 适配器 - i2c_del_adapter 二.IIC 设…

【C++算法】11.滑动窗口_最大连续1的个数lll

文章目录 题目链接:题目描述:解法C 算法代码:图解 题目链接: 1004. 最大连续 1 的个数 III 题目描述: 解法 解法一:暴力枚举zero计数器 转化找出最长的子数组,0的个数不超过k个。 例如&#xf…

计算机网络——有连接传输层协议TCP

序号 序号一般不从0开始,这个在双方建立连接后约定一个数 这样做可以避免网络中滞留的TCP段对新的连接的干扰

Flutter状态管理

StatefulWidget按状态划分StatelessWidgetStatefulWidget 按照作用域划分组件内私有状态实现跨组件状态管理全局状态 状态组件的组成 DataTableInheritedWidget生命周期无状态组件有状态组件initState()didChangeDependencies()build()setState()didUpdateWidget()deactivate()…

Redis 集群 总结

前言 相关系列 《Redis & 目录》(持续更新)《Redis & 集群 & 源码》(学习过程/多有漏误/仅作参考/不再更新)《Redis & 集群 & 总结》(学习总结/最新最准/持续更新)《Redis & 集群…

二十二、Python基础语法(模块)

模块(module):在python中,每个代码文件就是一个模块,在模块中定义的变量、函数、类别人都可以直接使用,如果想要使用别人写好的模块,就必须先导入别人的模块,模块名须满足标识符规则(由字母、数…

【国潮来袭】华为原生鸿蒙 HarmonyOS NEXT(5.0)正式发布:鸿蒙诞生以来最大升级,碰一碰、小艺圈选重磅上线

在昨日晚间的原生鸿蒙之夜暨华为全场景新品发布会上,华为原生鸿蒙 HarmonyOS NEXT(5.0)正式发布。 华为官方透露,截至目前,鸿蒙操作系统在中国市场份额占据 Top2 的领先地位,拥有超过 1.1 亿 的代码行和 6…

布隆过滤器:极简存储,高效检索

引言 在海量数据的存储与检索中,如何在保持快速检索的同时,降低内存占用是个巨大的挑战。有没有一种既能快速检索又能节省内存的方案?布隆过滤器(Bloom Filter)就是这样一种数据结构。 布隆过滤器的基本原理 如果我…

Vue.js 学习总结(11)—— Vue3 Hook 函数实战总结

前言 在 Vue 3 中,Hook 函数是一种特殊的函数,用于封装可重用的逻辑和状态管理。Hook 函数允许你在 Vue 组件中提取和复用逻辑,而不是将所有逻辑都放在组件的选项对象中。它们可以帮助你更好地组织代码,提高代码的可维护性和可测…

算法题总结(十九)——图论

图论 DFS框架 void dfs(参数) { if (终止条件) {存放结果;return; }for (选择:本节点所连接的其他节点) {处理节点;dfs(图,选择的节点); // 递归回溯,撤销处理结果 } }深搜三部曲 确认递归函数,参数确认终止条件处理目前搜索节…

JAVA基础:IO流 (学习笔记)

IO流 一,IO流的理解 i : input 输入 o:output 输入 流:方式,传递数据的方式---------相当于生活中的“管道”,“车”,将资源从一个位置,传递到另一个位置 二,IO流的分…