ConcurrentHashMap 1.7与1.8底层源码的区别

1. 锁结构

  • JDK 1.7
    • 使用基于 Segment 的分段锁机制。Segment 是 ReentrantLock 的子类,每个 Segment 维护了一个 HashEntry 数组,这些 Segment 构成了一个 Segment 数组。因此,ConcurrentHashMap 实际上是通过分段锁来实现的,每个 Segment 都可以独立地加锁和解锁,从而提高了并发性能。
    • 锁的粒度是段级别的,即每个 Segment 锁定的是一段哈希表结构。
  • JDK 1.8
    • 摒弃了 Segment,转而使用 synchronized 关键字结合 CAS(Compare-And-Swap)操作来实现线程安全。
    • 锁的粒度缩小为结点锁,即只对实际发生冲突的节点进行加锁,从而进一步提高了并发性能。

2. 数据结构

  • JDK 1.7
    • 使用 Segment 数组 + HashEntry 数组的结构。
    • 每个 Segment 内部维护了一个 HashEntry 数组,用于存储键值对。
  • JDK 1.8
    • 使用 Node 数组 + 链表 + 红黑树的结构。
    • 当链表长度过长时(默认大于等于8),会转换为红黑树,以提高查询效率。

3. put 方法的执行流程

  • JDK 1.7
    • 需要进行两次定位:先对 Segment 进行定位,再对其内部的 HashEntry 数组下标进行定位。
    • 定位后,采用自旋锁 + 锁膨胀的机制进行加锁。如果自旋次数超过一定阈值(默认为64),则会发生锁膨胀,线程将陷入阻塞状态,等待唤醒。
    • 在整个 put 操作期间都持有锁。
  • JDK 1.8
    • 只需要一次定位,直接定位到 Node 数组的下标。
    • 如果对应下标处没有节点,说明没有发生哈希冲突,此时直接通过 CAS 进行插入。
    • 如果插入失败(即存在哈希冲突),则使用 synchronized 对该节点进行加锁,然后插入节点。

4. 计算 size 的方法

  • JDK 1.7
    • 尝试在不加锁的情况下进行统计。如果前两次统计的结果相同,则直接返回该结果。
    • 如果统计结果超过三次仍不一致,则对每个 Segment 进行加锁后再进行统计。
  • JDK 1.8
    • 维护一个 baseCount 属性来记录节点数量。
    • 每次 put 操作后,都会通过 CAS 自增 baseCount
    • 由于并发环境下集合的大小是动态变化的,因此 size() 方法提供的是一个估计值,并不保证完全准确。
  • 总结

  • 总结来说,JDK 1.8 相比于 JDK 1.7,主要区别在于:

  • 结构上取消了 Segment 数组,改用数组 + 链表 + 红黑树结构。
  • 线程安全机制由 Segment 的分段锁变为 CAS + synchronized
  • 锁粒度由 Segment 级别变为单个 Node 级别。
  • 引入了红黑树来优化长链表的性能问题。
  • size 方法的实现不同,JDK 1.8 使用了更高效的统计方式
  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值