ConcurrentHashMap(JDK1.8)为什么要放弃Segment

今天看到一篇博客:jdk1.8的HashMap和ConcurrentHashMap,我想起了前段时间面试的一个问题:ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

我想从下面几个角度讨论这个问题:

  1. 锁的粒度
    首先锁的粒度并没有变粗,甚至变得更细了。每当扩容一次,ConcurrentHashMap的并发度就扩大一倍。
  2. Hash冲突
    JDK1.7中,ConcurrentHashMap从过二次hash的方式(Segment -> HashEntry)能够快速的找到查找的元素。在1.8中通过链表加红黑树的形式弥补了put、get时的性能差距。
  3. 扩容
    JDK1.8中,在ConcurrentHashmap进行扩容时,其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,减小了扩容的粒度,提高了扩容的效率。

下面是我对面试中的那个问题的一下看法:

为什么是synchronized,而不是可重入锁
1. 减少内存开销
假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
2. 获得JVM的支持
可重入锁毕竟是API这个级别的,后续的性能优化空间很小。
synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。


我不能保证写的东西完全正确
但是我能保证我写的东西都是经过思考之后所得出,而不是复制粘贴得来
如果有不对的地方,希望您能够指正,谢谢~

### JDK 1.7 和 JDK 1.8ConcurrentHashMap 的区别 #### 数据结构变化 在 JDK 1.7 版本中,`ConcurrentHashMap` 使用分段锁定机制来提高并发性能。整个哈希表被分割成多个独立的部分(Segment),每个 Segment 类似于一个小型的 HashMap,并且有自己独立的锁。这使得不同部分可以并行访问而不会相互阻塞。 而在 JDK 1.8 及之后版本里,为了进一步提升性能和减少内存占用,引入了一种新的设计——基于 CAS (Compare And Swap) 操作以及链表转红黑树优化策略。当桶内元素数量超过一定阈值时会自动转换为红黑树形式存储键值对,从而提高了查找速度[^4]。 #### 锁粒度调整 由于采用了更细粒度的数据分区方式,在处理高并发场景下能够显著降低争用率;同时通过无锁算法实现了大部分操作无需加锁即可完成,特别是对于只读请求来说几乎不存在同步开销问题[^2]。 ```java // JDK 1.7 示例代码片段 public V get(Object key) { int hash = hash(key); Segment<K,V> s = segmentFor(hash); // 获取对应的segment对象 return s.get(key, hash); // 调用segment内部方法获取value } // JDK 1.8 示例代码片段 final V doGet(Object key, int hash) { Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; if ((tab = table) != null && (n = tab.length) > 0 && (e = tab[(n - 1) & hash]) != null) { ... } } ``` #### 性能改进 随着新架构的应用,JDK 1.8 下 `ConcurrentHashMap` 表现出更好的扩展性和更低延迟特性。特别是在多核处理器环境中运行大型应用程序时优势明显,测试结果显示其吞吐量远高于旧版实现。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值