ConcurrentHashMap
和 Hashtable
都是线程安全的 Map
实现,但它们的线程安全机制和性能特性有所不同。以下是二者的主要区别:
1. 锁机制的不同
1.1 Hashtable
- 全表加锁:
Hashtable
使用的是 synchronized 关键字来保证线程安全。具体来说,所有对Hashtable
的读写操作都在整个表上加锁,也就是说,任何线程要执行任何操作(例如put
或get
)都必须获取到Hashtable
的锁。这意味着只有一个线程可以同时访问Hashtable
。 - 性能问题:由于每次操作都需要获取锁,且是对整个
Hashtable
的锁定,因此在高并发环境下,多个线程访问时可能会造成大量的线程阻塞,性能较差。
1.2 ConcurrentHashMap
- 分段锁(锁分段)机制:
ConcurrentHashMap
采用了更细粒度的锁机制,基于 分段锁(Java 8 之前的实现)或 CAS(Compare-And-Swap)和 volatile 变量(Java 8 及以后的实现)。- 在 Java 7 及之前,
ConcurrentHashMap
将内部数据分为多个 段(Segment),每个段管理一部分Map
数据,锁是作用在段级别而不是整个Map
上。因此,多个线程可以并发访问不同段的数据,减少了锁竞争。 - 在 Java 8 及以后,
ConcurrentHashMap
采用了 CAS 和 分段锁结合的无锁算法 来确保线程安全。对于put
和get
操作,它仅锁定需要更新的部分或使用CAS
操作,而不是锁住整个结构。
- 在 Java 7 及之前,
- 并发访问能力强:由于
ConcurrentHashMap
的锁粒度更细,可以实现更高的并发性,多个线程可以同时访问不同部分的数据,从而大大提高性能。
2. 性能表现
- Hashtable:由于使用的是全表锁,因此在高并发环境下性能会非常低,多个线程争抢锁会造成线程阻塞,吞吐量较低。
- ConcurrentHashMap:由于使用了更细粒度的锁或无锁的 CAS 机制,因此在高并发环境下,多个线程可以同时进行读写操作,性能表现明显优于
Hashtable
。ConcurrentHashMap
是设计用于高并发场景的,随着线程数量增加,它的性能优势越明显。
3. Null 键和值的处理
-
Hashtable:不允许
null
键或null
值。如果试图插入null
键或null
值,Hashtable
会抛出NullPointerException
。 -
ConcurrentHashMap:同样不允许
null
键或null
值。如果尝试插入null
键或null
值,ConcurrentHashMap
也会抛出NullPointerException
。
4. 迭代时的安全性
-
Hashtable:使用的是直接的同步锁定,因此在迭代期间,如果有其他线程对
Hashtable
进行修改操作,会抛出ConcurrentModificationException
,它不支持迭代时的修改。 -
ConcurrentHashMap:使用了一种 弱一致性 的迭代方式,即迭代器不会抛出
ConcurrentModificationException
,即使在迭代过程中有其他线程修改了Map
,它也不会抛异常,并且能看到其他线程的最新修改。这种弱一致性确保了高并发场景下的迭代安全。
5. 使用场景
-
Hashtable:由于性能问题和设计过时,现在已经很少使用,通常不推荐在新的项目中使用
Hashtable
。它适用于一些非常简单的、低并发要求的场景。 -
ConcurrentHashMap:专门为高并发场景设计,性能优越,非常适合在并发编程中使用。例如,在需要高并发的缓存实现、共享状态管理等场景中,
ConcurrentHashMap
是一个非常好的选择。
6. 线程安全级别
-
Hashtable:所有操作都加锁,保证了完全线程安全,但代价是较低的并发性能。
-
ConcurrentHashMap:通过分段锁或无锁化实现,只在需要时加锁,部分操作是无锁的(如读取操作),因此在保证线程安全的同时,也实现了较高的并发性。
7. 扩容机制的差异
-
Hashtable:在需要扩容时,所有操作都会被阻塞,扩容期间整个表会被锁定,直到扩容完成。
-
ConcurrentHashMap:采用 渐进式扩容(Java 8 之后),即在扩容时,它会逐渐将元素迁移到新的桶中,而不是一次性锁定整个表进行扩容。这样可以确保扩容过程中的并发性,不会阻塞所有操作。
总结
特性 | Hashtable | ConcurrentHashMap |
---|---|---|
锁机制 | 全表加锁,使用 synchronized | 分段锁(Java 7)或 CAS(Java 8+) |
并发性 | 低,并发线程较多时性能差 | 高并发性能优越 |
允许 null 键/值 | 不允许 | 不允许 |
迭代器 | 抛出 ConcurrentModificationException | 弱一致性,允许并发修改 |
使用场景 | 老旧,已不推荐使用 | 适用于高并发场景 |
扩容机制 | 全表锁定,性能差 | 渐进式扩容,性能好 |
因此,ConcurrentHashMap
是现代 Java 开发中常用的并发 Map
实现,具备良好的性能表现和较高的线程安全性,而 Hashtable
因为其全表加锁机制,已经过时,通常不再推荐使用。