目录
3.ConcurrentHashMap和HashTable的对比表
HashMap本身是不安全的,所以要在多线程下使用哈希表,我们可以使用:
• Hashtable
• ConcurrentHashMap
1.HashTable
(1)基本概念
Hashtable 是 Java 早期提供的线程安全哈希表实现(java.util.Hashtable
),通过 全局锁(synchronized) 保证线程安全。
简单来说,HashTable就是简单地把方法加上了synchronized关键字。这等于是直接对HashTable对象本身加锁。
但是HashTable的性能较低:
• 如果多线程访问同⼀个Hashtable就会直接造成锁冲突。
• size属性也是通过synchronized来控制同步,也是⽐较慢的。
• ⼀旦触发扩容,就由该线程完成整个扩容过程.这个过程会涉及到⼤量的元素拷⻉,效率会⾮常低。
(2) 使用背景
由于HashTable直接对整个哈希表加锁,虽然保证了线程安全问题,但是也让HashTable的效率极低:
当任意两个线程同时访问哈希表上的任意数据时就会触发锁竞争。但是,如果两个线程分别访问哈希表上的不同链表时,不会触发线程安全问题,但是HashTable却不允许这种情况发生。
Hashtable
是 Java 早期线程安全哈希表的实现,通过 全局锁 保证线程安全,但其粗粒度锁机制导致高并发场景下性能低下,已被 ConcurrentHashMap
取代。
现代开发中应避免使用 Hashtable
,除非维护遗留代码或处理极低并发的简单需求。理解其设计缺陷(如锁竞争、扩容阻塞)有助于更好地选择高性能并发容器(如 ConcurrentHashMap
)。
2.ConcurrentHashMap
(1)基本概念
ConcurrentHashMap 是 Java 中用于高并发场景的线程安全哈希表实现,属于 java.util.concurrent
包。它在多线程环境下提供了高效的并发读写能力,同时避免了传统同步容器(如 Hashtable
或 Collections.synchronizedMap
)的性能瓶颈。
(2)核心设计目标
-
线程安全:支持多线程并发读写,无需外部同步。
-
高吞吐量:通过细粒度锁或无锁操作(CAS)减少竞争。
-
弱一致性迭代器:迭代过程中允许其他线程修改数据,不会抛出
ConcurrentModificationException
。 -
可扩展性:在数据量增大时,性能不会显著下降。
(3) 设计原理
在 Java 8 之前,ConcurrentHashMap
使用 分段锁(Segment) 机制(类似于分桶锁)。分段锁是将所有链表分成一段一段的,针对每一段进行加锁,相比于对整个哈希表加锁来说提高了效率。
Java 8 及以后 改为基于 CAS(Compare-And-Swap) 和 synchronized 锁 的优化设计,核心改进如下:
1)读写操作
-
写操作:
-
插入(
put
)、删除(remove
)等写操作仅锁定当前桶的头节点(锁桶)。 -
若桶结构为红黑树,则锁定树的根节点。
-
-
读操作:
无锁(基于volatile
变量和内存可见性),支持高并发读。
2)原子性
提供了一系列原子操作,充分利用了CAS:
-
putIfAbsent(key, value)
:仅当键不存在时插入。 -
replace(key, oldValue, newValue)
:条件替换。 -
compute(key, remappingFunction)
:原子计算新值。
3)扩容机制
-
多线程协同扩容:
当表需要扩容时,其他线程可以协助完成数据迁移,避免单线程阻塞。 -
扩容期间读写兼容:
读操作无需加锁,可直接访问旧表或新表;写操作若遇到迁移中的桶,会协助迁移。迁移期间新表和旧表同时存在,知道扩容完毕,删除旧表。 -
插入和查找:
在此期间,插入只往新表中插入;查找需要在新表和旧表中同时查找。
3.ConcurrentHashMap和HashTable的对比表
场景 | Hashtable / synchronizedMap | ConcurrentHashMap |
---|---|---|
读操作 | 全局锁,串行化 | 无锁,完全并发 |
写操作 | 全局锁,串行化 | 桶级锁,多线程可并行写入不同桶 |
扩容 | 单线程扩容,阻塞所有操作 | 多线程协同扩容,读写不阻塞 |
内存一致性 | 强一致性 | 弱一致性(更高吞吐量) |