目录
[2.1 HashMap(无锁)](#2.1 HashMap(无锁))
[2.2 HashTable(全局独占锁)](#2.2 HashTable(全局独占锁))
[2.3 ConcurrentHashMap:细粒度锁 + 无锁并发(性能最优)](#2.3 ConcurrentHashMap:细粒度锁 + 无锁并发(性能最优))
[1. 为什么 HashMap 允许 null 键值,而另外两者不允许?](#1. 为什么 HashMap 允许 null 键值,而另外两者不允许?)
[2. 为什么不推荐用 HashTable?](#2. 为什么不推荐用 HashTable?)
我们在学习Java的过程中,通常遇到算法类的问题时会用到HashMap 的数据结构,在单线程的情况下,HashMap确实是最优选;但如果是多线程,那么HashMap就会造成一定的线程安全问题,所以为了解决线程安全问题,引入HashTable 和ConcurrentHashMap就可以更好的应对线程安全问题。
1.核心特性区别
- HashMap:非线程安全 ,允许key值为null(但只能1个key为null),单线程下性能高,适用于单线程的所有情况,适合日常开发。
- **HashTable:**线程安全 ,不允许key值为null,锁密度极大,性能极低,已过时废弃。
- ConcurrentHashMap:线程安全 ,性能优异,不允许key值为null,极大降低了锁冲突概率,专为多线程高并发场景设计,代替HashTable并发容器。
2.锁机制与并发原理区别
三者最大的差异本质是线程安全的实现方式不同,直接决定性能高低:
2.1 HashMap(无锁)
全程没有任何锁机制,单线程高效读写。但是在多线程环境下,会出现数据覆盖、扩容死循环、元素丢失 等线程安全问题,记住,HashMap绝对不能直接用于多线程场景。
2.2 HashTable(全局独占锁)
HashTable 所有增删改查方法都直接加synchronized全局对象锁,同一时间只有一个线程能操作哈希表。无论读写、操作哪个数据,都会阻塞其他所有线程,锁粒度极大、并发度为 1,高并发场景下竞争激烈、性能极差。

从上图的源码中就可以看出HashTable就是给所有的方法都暴力的加上synchronized关键字,物极必反,线程安全性确实保证了,但是大大提高了锁冲突的概率,所有效率极低。
2.3 ConcurrentHashMap:细粒度锁 + 无锁并发(性能最优)
专门为高并发优化而设计的,摒弃 HashTable 的全局锁,实现读写分离、高并发并行:
**✅JDK1.7( 分段锁):**将容器分为 16 个独立 Segment,多线程可同时操作不同分段,互不阻塞,并发度提升 16 倍
✅JDK1.8( 桶级锁 + CAS): 读操作无锁(依靠 volatile 保证可见性),写操作仅锁定当前操作的数组桶,其他桶可正常并发读写;结合 CAS 无锁操作,极大降低锁竞争
✅迭代器为弱一致性,遍历过程中数据修改不会抛出并发修改异常,适配并发遍历场景
3.疑惑解答
1. 为什么 HashMap 允许 null 键值,而另外两者不允许?
**HashMap:**单线程场景,null 键会固定存储在数组下标 0 位置,开发者可清晰区分 null 键和普通键,无并发歧义。
**HashTable/ConcurrentHashMap:**多线程场景下,无法区分「key 为 null」和「key 不存在」两种情况,会导致业务逻辑歧义;同时 null 操作会触发空指针异常,因此直接禁止 null 键值,规避并发隐患。
2. 为什么不推荐用 HashTable?
- HashTable 存在致命缺陷:全局锁导致并发性能极差,无红黑树优化、结构老旧、不支持 null 值,且官方已标记过时。多线程场景优先使用 ConcurrentHashMap,单线程优先 HashMap,完全可以替代 HashTable 所有使用场景。
4.常见面试题
- 💬ConcurrentHashMap 的读是否要加锁,为什么?
答: 读操作没有加锁,目的是为了进一步降低锁冲突的概率。为了保证读到刚修改的数据,搭配了 volatile 关键字。
- 💬介绍下 ConcurrentHashMap 的锁分段技术?
**答:**这个是 Java1.7 中采取的技术,Java1.8 中已经不再使用了。简单的说就是把若干个哈希桶分成一个 "段"(Segment),针对每个段分别加锁。 目的也是为了降低锁竞争的概率。当两个线程访问的数据恰好在同一个段上的时候,才触发锁竞争。
- 💬 ConcurrentHashMap 在 jdk1.8 做了哪些优化?
答: 取消了分段锁,直接给每个哈希桶 (每个链表) 分配了一个锁 (就是以每个链表的头结点对象作为锁对象)。 将原来数组 + 链表的实现方式改进成数组 + 链表 / 红黑树的方式。当链表较长的时候 (大于等于 8 个元素) 就转换成红黑树。
- 💬 Hashtable 和 HashMap、ConcurrentHashMap 之间的区别?
答:
- HashMap: 线程不安全,key 允许为 null
- Hashtable: 线程安全,使用 synchronized 锁 Hashtable 对象,效率较低,key 不允许为 null。
- ConcurrentHashMap: 线程安全,使用 synchronized 锁每个链表头结点,锁冲突概率低,充分利用 CAS 机制,优化了扩容方式,key 不允许为 null。
总结下来就是,单线程选HashMap,多线程选ConcurrentHashMap,HashTable性能最低一般不采用。觉得有帮助的小伙伴不妨点个关注再走叭~