HashMap 本身不是线程安全的。
在多线程环境下使用哈希表可以使用:
- Hashtable
- ConcurrentHashMap
一、HashTable
HashTable 只是简单的把关键方法加上了synchronized 关键字。
这相当于直接针对 Hashtable 对象本身加锁,任意操作就会涉及到对this的加锁。
- 如果多线程访问同一个 Hashtable 就会直接造成锁冲突
- size 属性也是通过 synchronized 来控制同步, 也是比较慢的
- 一旦触发扩容, 就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝, 效率会非常低
二、ConcurrentHashMap
相比于Hashtable 做出了一系列的改进和优化,以Java1.8 为例:
- 读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁,加锁的方式仍然是是用 synchronized, 但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率。
- 充分利用 CAS 特性,(前篇文章给大家详细介绍过), 比如 size 属性通过 CAS 来更新,避免出现重量级锁的情况。
- 优化了扩容方式: 化整为零
- 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去。
- 扩容期间, 新老数组同时存在。
- 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程,每个操作负责搬运一小部分元素。
- 搬完最后一个元素再把老数组删掉。
- 插入只往新数组加。
- 查找需要同时查新数组和老数组。
三、相关问题
3.1 ConcurrentHashMap的读是否要加锁,为什么
读操作没有加锁,目的是为了进一步降低锁冲突的概率。为了保证读到刚修改的数据, 搭配了
volatile 关键字
3.2 ConcurrentHashMap的锁分段技术
简单的说就是把若干个哈希桶分成一个 "段" (Segment), 针对每个段分别加锁。目的也是为了降低锁竞争的概率,当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争。
3.3 ConcurrentHashMap在jdk1.8做了哪些优化?
- 取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对象)。
- 将原来数组 + 链表的实现方式改进成 数组 + 链表 / 红黑树 的方式,当链表较长的时候(大于等于8 个元素)就转换成红黑树。
3.4 Hashtable和HashMap、ConcurrentHashMap 之间的区别?
- HashMap: 线程不安全,key 允许为 null
- Hashtable: 线程安全。使用 synchronized 锁 Hashtable 对象, 效率较低。 key 不允许为 null
- ConcurrentHashMap: 线程安全。使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用CAS 机制, 优化了扩容方式, key 不允许为 null
🌈🌈🌈好啦,今天的分享就到这里!
🛩️🛩️🛩️希望各位看官读完文章后,能够有所提升。
🎉🎉🎉创作不易,还希望各位大佬支持一下!
✈️✈️✈️点赞,你的认可是我创作的动力!
⭐⭐⭐收藏,你的青睐是我努力的方向!
✏️✏️✏️评论:你的意见是我进步的财富!