多线程进阶-线程安全的集合类
目录
🥇多线程环境使⽤ ArrayList
🥈多线程环境使⽤队列
🥉多线程环境使⽤哈希表
🏆相关面试题
原来的集合类,大部分都不是线程安全的
Vector,Stack,HashTable,是线程安全的(不建议用),其他的集合类不是线程安全的
🥇多线程环境使⽤ ArrayList
1. ⾃⼰使⽤同步机制 (synchronized 或者 ReentrantLock),自己加锁
2. Collections.synchronizedList(new ArrayList);
synchronizedList 是标准库提供的⼀个基于 synchronized 进⾏线程同步的 List. synchronizedList 的关键操作上都带有 synchronized
3. 使⽤ CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。
🥛当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Coy,复制
出一个新的容器,然后新的容器里添加元素,
🥛添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添
加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
优点:
在读多写少的场景下,性能很高,不需要加锁竞争
缺点:
1.占用内存较多
2.新写的数据不能被第一时间读取到.
🥈多线程环境使⽤队列
1.ArrayBlockingQueue
基于数组实现的阻塞队列
2.LinkedBlockingQueue
基于链表实现的阻塞队列
3.PriorityBlockingQueue
基于堆实现的带优先级的阻塞队列
4.TransferQueue
最多只包含一个元素的阻塞队列
🥉多线程环境使⽤哈希表
HashMap本身不是线程安全的.
在多线程环境下使用哈希表可以使用:
●Hashtable(不推荐)
●ConcurrentHashMap(Java方向top5 超高频面试题)🎯🎯🎯
此处的这个数据结构,相比于HashMap和Hashtable来说,改进力度非常的
☃️优化了锁的粒度[最核心]
读操作没有加锁(但是使用了volatile保证从内存读取结果),只对写操作进行加锁.加锁的方式仍然是用synchronized,但是不是锁整个对象,而是"锁桶"(用每个链表的头结点作为锁对象),大大降低了锁冲突的概率
❄️充分利⽤ CAS 特性. ⽐如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.
🌊针对读操作,做了特殊处理。上述的加锁,只是针对写操作,加锁。对于读操作的话,通过volatile以及一些精巧的代码实现,确保读操作,不会读到"修改一半的数据"。
💦优化了扩容⽅式: 化整为零
普通hash表扩容,需要创建新的hash表,把元素都搬运过去。这一系列操作,很有可能就在一次put就完成了,就会使这次put开销非常大,耗时非常长.
🌏发现需要扩容的线程,只需要创建一个新的数组,同时只搬几个元素过去.
🌏扩容期间,新老数组同时存在.
🌏后续每个来操作ConcurrentHashMap的线程,都会参与搬家的过程.每个操作负责搬运一小部分元素
🌏搬完最后一个元素再把老数组删掉.
🌏这个期间,插入只往新数组加。
🌏这个期间,查找需要同时查新数组和老数组
🏆相关面试题
1️⃣ConcurrentHashMap的读是否要加锁,为什么?
读操作没有加锁. ⽬的是为了进⼀步降低锁冲突的概率. 为了保证读到刚修改的数据, 搭配了 volatile 关键字.
2️⃣介绍下 ConcurrentHashMap的锁分段技术?
这个是 Java1.7 中采取的技术. Java1.8 中已经不再使⽤了. 简单的说就是把若⼲个哈希桶分成⼀个 "段" (Segment), 针对每个段分别加锁.
⽬的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同⼀个段上的时候, 才触发锁竞争.
3️⃣ConcurrentHashMap在jdk1.8做了哪些优化?
取消了分段锁, 直接给每个哈希桶(每个链表)分配了⼀个锁(就是以每个链表的头结点对象作为锁对象).
将原来 数组 + 链表 的实现⽅式改进成 数组 + 链表 / 红⿊树 的⽅式. 当链表较⻓的时候(⼤于等于 8 个元素)就转换成红⿊树.
4️⃣Hashtable和HashMap、ConcurrentHashMap 之间的区别?
HashMap: 线程不安全. key 允许为 null
Hashtable: 线程安全. 使⽤ synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null.ConcurrentHashMap: 线程安全. 使⽤ synchronized 锁每个链表头结点, 锁冲突概率低, 充分利⽤ CAS 机制. 优化了扩容⽅式. key 不允许为 null