当前位置: 首页 > article >正文

【JavaEE初阶】 线程安全的集合类

文章目录

  • 🍀前言
  • 🌲多线程环境使用 ArrayList
    • 🚩自己使用同步机制 (synchronized 或者 ReentrantLock)
    • 🚩Collections.synchronizedList(new ArrayList);
    • 🚩使用 CopyOnWriteArrayList
  • 🎍多线程环境使用队列
  • 🎋多线程环境使用哈希表
    • 🚩Hashtable
    • 🚩ConcurrentHashMap
  • 🌳相关面试题
  • ⭕总结

🍀前言

原来的集合类, 大部分都不是线程安全的.

Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的.

为什么不建议使用呢?

因为我们在使用的时候,这些类就会自动的加锁,虽然编译器会自动优化为没有锁竞争的线程进行锁消除的优化,但是呢万一编译器没有优化好,就麻烦了

所以我们更多的是搭配一下其他方案达到线程安全,比如以下几种

🌲多线程环境使用 ArrayList

🚩自己使用同步机制 (synchronized 或者 ReentrantLock)

这里博主就不做过多赘述了,不懂得宝子可以去看一下博主对着方面的讲解

🚩Collections.synchronizedList(new ArrayList);

  • synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的List.

  • synchronizedList 的关键操作上都带有 synchronized

🚩使用 CopyOnWriteArrayList

使用CopyOnWrite容器即写时复制的容器

  • 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,

  • 添加完元素之后,再将原容器的引用指向新的容器

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

优点:

在读多写少的场景下, 性能很高, 不需要加锁竞争.

缺点:

  1. 占用内存较多.

  2. 新写的数据不能被第一时间读取到

🎍多线程环境使用队列

这个博主在讲【JavaEE初阶】 阻塞式队列详解中也有提到,所以这里只是简单列举

  1. ArrayBlockingQueue
    基于数组实现的阻塞队列

  2. LinkedBlockingQueue
    基于链表实现的阻塞队列

  3. PriorityBlockingQueue
    基于堆实现的带优先级的阻塞队列

  4. TransferQueue
    最多只包含一个元素的阻塞队列

🎋多线程环境使用哈希表

HashMap 本身不是线程安全的.

在多线程环境下使用哈希表可以使用:

  • Hashtable

  • ConcurrentHashMap

🚩Hashtable

Hashtable在实现过程中只是简单的把关键方法加上了 synchronized 关键字.
在这里插入图片描述
在这里插入图片描述

这相当于直接针对 Hashtable 对象本身加锁.

  • 如果多线程访问同一个 Hashtable 就会直接造成锁冲突.

  • size 属性也是通过 synchronized 来控制同步, 也是比较慢的.

  • 一旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到大量的元素拷贝, 效率会非常低
    在这里插入图片描述

🚩ConcurrentHashMap

相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例

  • 读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁. 加锁的方式仍然是是用 synchronized, 但是不是锁整个对象, 而是 “锁桶” (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率.

  • 充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.

优化了扩容方式: 化整为零

  • 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去.

  • 扩容期间, 新老数组同时存在.

  • 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小部分元素.

  • 搬完最后一个元素再把老数组删掉.

  • 这个期间, 插入只往新数组加.

  • 这个期间, 查找需要同时查新数组和老数组
    在这里插入图片描述
    想要详细了解的读者可以去ConcurrentHashMap源码分析(JDK8版本)进行查看

🌳相关面试题

  1. ConcurrentHashMap的读是否要加锁,为什么?

读操作没有加锁. 目的是为了进一步降低锁冲突的概率. 为了保证读到刚修改的数据, 搭配了 volatile 关键字.

  1. 介绍下 ConcurrentHashMap的锁分段技术?

这个是 Java1.7 中采取的技术. Java1.8 中已经不再使用了. 简单的说就是把若干个哈希桶分成一个 “段” (Segment),针对每个段分别加锁. 目的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争.

  1. ConcurrentHashMap在jdk1.8做了哪些优化?

取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对 象). 将原来 数组 + 链表的实现方式改进成 数组 + 链表 / 红黑树 的方式. 当链表较长的时候(大于等于 8 个元素)就转换成红黑树.

  1. Hashtable和HashMap、ConcurrentHashMap 之间的区别?
  • HashMap: 线程不安全. key 允许为 null

  • Hashtable: 线程安全. 使用 synchronized 锁,Hashtable 对象, 效率较低. key 不允许为 null.

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

⭕总结

关于《【JavaEE初阶】 线程安全的集合类》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!


http://www.kler.cn/a/105323.html

相关文章:

  • elementUI 中 date-picker 的使用的坑(vue3)
  • Redis | 数据结构(02)SDS
  • 解决Windows出现找不到mfcm90u.dll无法打开软件程序的方法
  • 第四章 文件管理 六、文件的基本操作
  • 第十一届蓝桥杯模拟赛第一期
  • 用IntelliJ远程打断点调试
  • Mingw下载---运行vscodeC++文件
  • VNC图形化远程连接Ubuntu服务器
  • vue首页多模块布局(标题布局)
  • 在Java中使用FileReader.read()进行读取文件时,为什么乱码?两个方法解决
  • ROS学习记录2018.7.10
  • 使用java mail SMTPTransport发送邮箱,本地秒到,一上服务器就20-30s左右,生产环境直接发送失败。
  • Python基础学习004——for循环与字符串
  • 一、Docker Compose——什么是 Docker Compose
  • mongodb-gridfs下载文件报Sort exceeded memory limit of 104857600 bytes异常
  • MSQL系列(七) Mysql实战-SQL语句Join,exists,in的区别
  • 【顺序栈的表示和实现,顺序栈的初始化,是否为空,清空顺序栈,销毁顺序栈,】
  • Nginx的请求时间限制(如周一到周五可以访问)
  • Lua语言编写爬虫程序
  • ExcelPatternTool 开箱即用的Excel工具包现已发布!