你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
ConcurrentHashMap 是 Java 中用于并发场景的线程安全哈希表,它在高并发下提供了较好的性能,同时避免了线程间的冲突问题。
JDK 1.7:
基于 分段锁机制(Segment-based Locking)。
数据结构由多个 Segment 组成,每个 Segment 类似于一个小型的 HashMap,通过 ReentrantLock 实现线程安全。
每个 Segment 维护自己的哈希桶数组和链表/树结构。
JDK 1.8:
取消了 Segment,直接使用 CAS(Compare-And-Swap)+ synchronized。
数据结构为数组(Node[])+ 链表 + 红黑树。
在哈希冲突严重时,链表长度超过一定阈值(默认 8),会转换为红黑树,提升查询性能。
整个 ConcurrentHashMap 被分为多个 Segment,每个 Segment 独立维护其内部的哈希桶。
锁粒度更细:只锁住当前 Segment,避免了全表锁,从而提高并发性。
每次操作(如插入、删除、读取等)只需要对相关的 Segment 加锁。
采用 CAS + synchronized:
CAS 用于实现无锁操作,比如节点的插入。
当 CAS 操作失败时,退而使用 synchronized。
每个桶(数组中的一个槽)独立加锁,而非对整个结构加锁。
红黑树优化:当链表长度较长时,转为红黑树,提升性能。
根据 key 的 hash 值找到对应的桶(Node[] 的某个槽位)。
使用 CAS 尝试更新(如果桶为空,直接插入;否则进入下一步)。
如果 CAS 失败,则通过 synchronized 锁住对应的桶,并更新链表或红黑树。
通过计算哈希找到对应桶。
直接读取桶中的链表或红黑树,无需加锁(链表或树的读操作是线程安全的,因为写操作会使用同步机制避免冲突)。
通过哈希值找到对应桶。
使用 synchronized 锁住桶,安全地删除节点。
如果节点为红黑树节点,维护红黑树的平衡。
当负载因子超过阈值时,触发扩容(默认负载因子为 0.75)。
创建一个新的更大容量的数组。
数据迁移是分段完成的,多个线程可以并发地帮助扩容操作,减少停顿时间。
高效的线程安全性:通过细粒度锁和无锁操作,减少锁的竞争,提高并发性能。
支持高并发:多线程可以同时操作不同的桶或节点。
性能优于 Hashtable:Hashtable 使用的是全表锁,性能瓶颈显著,而 ConcurrentHashMap 采用分段锁或 CAS,性能大幅提升。
弱一致性:在读写操作并发的情况下,允许读取到旧数据(最终一致性),从而提升性能。
高并发环境下的 Key-Value 数据存储
如缓存系统、计数器、统计数据存储等。
需要线程安全的哈希表
替代 Hashtable 或 Collections.synchronizedMap()。
对性能要求较高的场景
如分布式系统的节点状态管理、会话管理等。
5.1 ConcurrentHashMap 是如何实现线程安全的?
通过分段锁(JDK 1.7)或 CAS + synchronized(JDK 1.8)实现线程安全。
5.2 为什么 JDK 1.8 放弃了分段锁?
分段锁结构复杂且限制了扩展性,JDK 1.8 改为更简单高效的 CAS + synchronized 机制。
5.3 ConcurrentHashMap 如何进行扩容?
在负载因子超过阈值时触发扩容,数据迁移由多个线程并发完成。
5.4 ConcurrentHashMap 的弱一致性是什么?
读操作可能会看到旧数据(但不抛出异常),写操作最终会生效。
5.5 ConcurrentHashMap 中的链表转红黑树的条件?
单个桶的链表长度超过阈值(默认 8)。
今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!