目录
一、区别
1. 继承关系
2. 存储的值
3. 线程安全
二、ConcurrentHashMap 相对于 Hashtable 的优化
1. 缩小锁粒度
2. 利用 CAS 特性
3. 优化扩容方式
三、不同版本 ConcurrentHashMap 的自我优化
一、区别
1. 继承关系
HashMap 类 | 继承自 AbstractMap 抽象类 |
Hashtable 类 | 继承自 Dictionary 抽象类 |
ConcurrentHashMap 类 | 继承自 AbstractMap 抽象类 |
2. 存储的值
HashMap 类 | key 值唯一,可以为 null |
Hashtable 类 | key 值唯一,不可以为 null |
ConcurrentHashMap 类 | key 值唯一,不可以为 null |
3. 线程安全
HashMap 类 | 线程不安全,方法没有加锁。 |
Hashtable 类 | 线程安全,但是是给整个实例对象加锁,访问效率低,Java 官方不推荐继续使用。 |
ConcurrentHashMap 类 | 线程安全,使用 synchronized 锁每个链表的头节点,锁冲突概率低,性能优于 Hashtable。 |
二、ConcurrentHashMap 相对于 Hashtable 的优化
1. 缩小锁粒度
- 读操作没有加锁(但是使用了 volatile 保证从内存读取结果),只对写操作进行加锁;使用 synchronized,但不是锁整个对象,而是锁链表头节点(”锁桶“),大大降低了锁冲突的概率。
2. 利用 CAS 特性
- 需要经常修改的属性(如 size 等),通过 CAS 更新,避免出现重量级锁。
3. 优化扩容方式
- 触发扩容后,同时存在新、旧两份哈希表。
- 插入操作直接往新哈希表中插入。
- 删除和查询操作会同时操作两份哈希表。
- 每个参与的线程都负责一部分的元素搬运工作。
- 扩容完成后,删除旧哈希表,保留新哈希表。
三、不同版本 ConcurrentHashMap 的自我优化
JDK1.7 中的 ConcurrentHashMap | 加锁使用的是分段锁机制,将哈希桶分成多个段,针对每个段分别加锁。目的是降低锁竞争。Java8 已经不再使用。 |
JDK1.8 中的 ConcurrentHashMap | 取消分段锁,给每个哈希桶(链表)分配锁,每个链表的头节点作为锁对象。 |
底层结构由数组+链表的形式,改进为数组+链表+红黑树,当链表节点数量大于等于8,且数组元素数量大于 64 时,超长的链表转为红黑树。 |