高并发神器!ConcurrentHashMap为何如此高效?

开发 开发工具
ConcurrentHashMap作为Java中一个重要的并发集合类,凭借其分段锁和CAS机制,在保证线程安全的同时,大大提升了性能。JDK 1.7中通过Segment的分段锁来降低锁竞争,而JDK 1.8中则进一步改进为无锁化操作和红黑树的结构,大幅度提升了性能和并发性。

引言

大家好,我是小米!今天我们来聊聊Java中一个超级实用的线程安全集合类——ConcurrentHashMap。对于多线程环境中需要频繁读写数据的场景来说,ConcurrentHashMap无疑是个好帮手。那么,为什么ConcurrentHashMap效率高?底层实现的奥秘又是什么?接下来,让我们一探究竟。

图片图片

ConcurrentHashMap与Hashtable的对比

在多线程环境中,我们常常需要保证数据的线程安全性。说到实现线程安全,ConcurrentHashMap和Hashtable都是不错的选择,但二者的性能表现却有很大差异。

Hashtable:同步锁的性能瓶颈

Hashtable作为Java早期的线程安全类,主要通过Synchronized关键字进行方法级别的同步来保证线程安全。比如,在执行put或get操作时,Hashtable会锁住整个对象,导致同一时间只能有一个线程访问或修改数据。这样虽然保证了安全性,但性能相对低下。

ConcurrentHashMap:分段锁的高效设计

ConcurrentHashMap的核心思想是分段锁,这使得它在性能上要远优于Hashtable。简单来说,ConcurrentHashMap将数据划分成多个段(Segment),每个Segment对应一个锁。不同线程访问不同Segment的数据时,可以同时进行而不互相阻塞,从而提高了并发性能。

Java的两个主要版本(1.7和1.8)对ConcurrentHashMap的底层结构有很大的差别,我们一起来看看它们的演变过程。

JDK 1.7:Segment分段锁

在JDK 1.7中,ConcurrentHashMap使用了分段锁(Segment)的设计。通过这一设计,ConcurrentHashMap达到了提高并发访问率的效果。

底层结构:Segment数组 + HashEntry链表

ConcurrentHashMap在底层将数据分为多个Segment,每个Segment内部由链表存储数据。这样一来,ConcurrentHashMap将整个Map分成了若干个小的子Map,每个Segment相当于一个小的Hashtable,持有一个独立的锁。因此,多个线程访问不同Segment的元素时不会相互影响,从而提高了并发性能。

如何实现分段锁?

ConcurrentHashMap中会对每一个键值对进行哈希计算,以确定它属于哪个Segment。每个Segment锁住一个区域的数据,这样每次只锁定一个Segment,即使一个Segment被锁定,其他Segment也可以同时被访问,这就避免了整个Map锁住的低效情况。

优缺点

  • 优点:提高了并发性能,多个线程可以同时操作不同的Segment。
  • 缺点:Segment数量(默认16个)固定后,无法动态扩容。即使并发再高,也无法突破这个限制。

JDK 1.8:无Segment,链表+红黑树+CAS

JDK 1.8中,ConcurrentHashMap的底层结构和实现方式发生了重大变化,Segment不再存在,取而代之的是更为精简的实现方式。JDK 1.8摒弃了Segment锁机制,而是采用了数组+链表+红黑树的组合数据结构。

数据结构:Node数组 + 链表/红黑树

JDK 1.8的ConcurrentHashMap与1.8版本的HashMap非常相似,底层通过一个Node数组来存储数据。如果某个桶中有大量hash冲突的数据,会先形成链表;当链表长度超过一定阈值(8)后,会转化成红黑树结构,从而提高查询效率。

并发控制:CAS + synchronized

ConcurrentHashMap 1.8 的线程安全主要通过CAS(Compare And Swap)和synchronized关键字来实现,而不是之前的锁住整个Segment。这样在进行增删改查时,只需要锁住当前操作的链表头部节点即可,大大降低了锁的粒度,进一步提升了并发效率。

  • CAS机制:CAS在检测到变量未被其他线程修改时,直接更新变量的值。相比传统的锁机制,CAS可以在无锁的情况下完成并发更新,大大提高了效率。
  • synchronized:当CAS无法保证安全性时,才会退而采用synchronized进行保护。JDK 1.8通过这种灵活的设计,进一步提升了并发性能。

优缺点

  • 优点:性能较JDK 1.7更优,不再依赖Segment;锁的粒度进一步缩小。
  • 缺点:实现较复杂,对内存占用和系统资源提出了更高的要求。

ConcurrentHashMap的核心机制剖析

1. get操作

get操作在ConcurrentHashMap中是无锁的,主要通过定位到具体的Node节点来直接获取数据。

流程:

  1. 首先通过hash值确定数据的位置。
  2. 若找到的桶是链表,则遍历链表寻找对应的节点。
  3. 若桶内为红黑树,则使用树的查找逻辑获取目标节点。

2. put操作

在执行put时,ConcurrentHashMap会尝试使用CAS来添加元素。如果当前节点位置为空,CAS更新会成功;否则,系统会退而使用synchronized锁住节点进行更新操作。

流程:

  • 计算key的hash值,定位到具体的桶。
  • 若该位置为空,则使用CAS将新值插入。
  • 若该位置已存在数据:

若为链表,遍历链表并添加至末尾;链表长度超过8则转化为红黑树。

若为红黑树,则按照红黑树的插入规则进行更新。

  • 如果容量超过阈值,则触发扩容。

3. 扩容机制

与HashMap类似,ConcurrentHashMap在容量不足时会进行扩容。不同的是,ConcurrentHashMap的扩容操作是分段进行的。

  • 分段扩容:在扩容过程中,多个线程可以协作进行桶数据迁移,而不是一个线程独自完成扩容,从而减少了线程阻塞。

ConcurrentHashMap的优势总结

  • 高并发性能:JDK 1.8后的ConcurrentHashMap通过CAS操作和synchronized,避免了全面锁的低效问题,锁的粒度更小,提高了整体并发性。
  • 高效数据结构:引入红黑树,提升了查询效率,使得冲突严重的情况下,依然能保持较高的访问效率。
  • 分段扩容:扩容过程可由多个线程协作进行,进一步提升了多线程环境下的性能表现。

END

ConcurrentHashMap作为Java中一个重要的并发集合类,凭借其分段锁和CAS机制,在保证线程安全的同时,大大提升了性能。JDK 1.7中通过Segment的分段锁来降低锁竞争,而JDK 1.8中则进一步改进为无锁化操作和红黑树的结构,大幅度提升了性能和并发性。

在实际开发中,如果你需要一个线程安全、高并发的Map集合,ConcurrentHashMap绝对是一个值得信赖的选择!希望今天的分享能够帮助大家更好地理解ConcurrentHashMap的底层设计及其优点,咱们下次再一起探讨更多Java黑科技!

责任编辑:武晓燕 来源: 软件求生
相关推荐

2024-02-26 00:00:00

JavaScript单线程高效

2009-04-14 19:30:08

虚拟化VmwareIT

2015-01-08 15:38:34

Fire Phone

2009-12-04 09:46:02

Linux操作系统

2021-01-26 16:21:46

边缘计算5GIoT

2024-09-18 05:30:00

GPU内存人工智能

2022-05-06 17:34:27

安全代码软件漏洞

2021-08-25 23:08:52

微服务编程IT

2014-08-08 15:36:58

2024-03-22 11:27:54

电缆管理数据中心

2022-06-08 13:11:35

比特币挖矿区块链

2010-07-16 16:06:11

求职

2012-05-10 10:23:10

技术人员开发

2009-09-17 13:06:24

2013-07-16 09:31:11

2012-04-24 14:41:15

HTML5

2021-08-30 14:23:41

身份验证隐私管理网络安全

2013-04-27 10:31:56

大数据全球技术峰会Hadoop

2017-10-25 14:28:27

Java工程师火爆原因

2019-03-25 07:35:51

委内瑞拉电网断电
点赞
收藏

51CTO技术栈公众号