JDK8的 CHM 为何放弃分段锁
概述
我们知道, 在 Java 5 之后,JDK 引入了 java.util.concurrent 并发包 ,其中最常用的就是 ConcurrentHashMap 了, 它的原理是引用了内部的 Segment ( ReentrantLock ) 分段锁,保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全的目的, 且效率大于 synchronized。
但是在 Java 8 之后, JDK 却弃用了这个分段锁策略,接下来详细学习一下。
一、jdk1.7分段锁的实现
和hashmap一样,在jdk1.7中ConcurrentHashMap的底层数据结构是数组加链表。和hashmap不同的是ConcurrentHashMap中存放的数据是一段段的,即由多个Segment(段)组成的。每个Segment中都有着类似于数组加链表的结构。
1.1 关于Segment
ConcurrentHashMap有3个参数:
- initialCapacity:初始总容量,默认16
- loadFactor:加载因子,默认0.75
- concurrencyLevel:并发级别,默认16
其中并发级别控制了Segment的个数,在一个ConcurrentHashMap创建后Segment的个数是不能变的,扩容过程过改变的是每个Segment的大小。
1.2 关于分段锁
段Segment继承了重入锁ReentrantLock,有了锁的功能,每个锁控制的是一段,当每个Segment越来越大时,锁的粒度就变得有些大了。
- 分段锁的优势在于保证在操作不同段 map 的时候可以并发执行,操作同段 map 的时候,进行锁的竞争和等待。这相对于直接对整个map同步synchronized是有优势的。
- 缺点在于分成很多段时会比较浪费内存空间(不连续,碎片化); 操作map时竞争同一个分段锁的概率非常小时,分段锁反而会造成更新等操作的长时间等待; 当某个段很大时,分段锁的性能会下降。
二、jdk1.8的map实现
和hashmap一样,jdk 1.8中ConcurrentHashmap采用的底层数据结构为数组+链表+红黑树的形式。数组可以扩容,链表可以转化为红黑树。
2.1 弃用原因
通过 JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:
- 加入多个分段锁浪费内存空间。
- 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
- 为了提高 GC 的效率.
2.2 什么时候扩容?
- 当前容量超过阈值
- 当链表中元素个数超过默认设定(8个),当数组的大小还未超过64的时候,此时进行数组的扩容,如果超过则将链表转化成红黑树
2.3 什么时候链表转化为红黑树?
当数组大小已经超过64并且链表中的元素个数超过默认设定(8个)时,将链表转化为红黑树
ConcurrentHashMap的put操作代码如下:
把数组中的每个元素看成一个桶。可以看到大部分都是CAS操作,加锁的部分是对桶的头节点进行加锁,锁粒度很小。
三、为什么不用ReentrantLock而用synchronized ?
- 减少内存开销:如果使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销,而1.8中只有头节点需要进行同步。
- 内部优化:synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。
参考资料
https://my.oschina.net/pingpangkuangmo/blog/817973
https://www.wanaright.com/2018/09/30/java10-concurrenthashmap-no-segment-lock/
https://cloud.tencent.com/developer/article/1509556
JDK8的 CHM 为何放弃分段锁的更多相关文章
- java8的ConcurrentHashMap为何放弃分段锁,为什么要使用CAS+Synchronized取代Segment+ReentrantLock
原文地址:https://cloud.tencent.com/developer/article/1509556 推荐一篇 ConcurrentHashMap 和 HashMap 写的比较的的文章 j ...
- 分段锁——ConcurrentHashMap
1.线程不安全的HashMap因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap. 2.效率低下的HashTable容 ...
- Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...
- 学习ConcurrentHashMap1.7分段锁原理
1. 概述 接上一篇 学习 ConcurrentHashMap1.8 并发写机制, 本文主要学习 Segment分段锁 的实现原理. 虽然 JDK1.7 在生产环境已逐渐被 JDK1.8 替代,然而一 ...
- redis分布式锁扣减库存弊端: 吞吐量低, 解决方法:使用 分段锁 分布式分段锁并发扣减库存--代码实现
package tech.codestory.zookeeper.aalvcai.ConcurrentHashMapLock; import lombok.AllArgsConstructor; im ...
- Java中的各种锁--分类总结
前言 本文需要具备一定的多线程基础才能更好的理解. 学习java多线程时,最头疼的知识点之一就是java中的锁了,什么互斥锁.排它锁.自旋锁.死锁.活锁等等,细分的话可以罗列出20种左右的锁,光是看着 ...
- 50道Java集合经典面试题(收藏版)
前言 来了来了,50道Java集合面试题也来啦~ 已经上传github: https://github.com/whx123/JavaHome 1. Arraylist与LinkedList区别 可以 ...
- Java面试,面试题
Java面试,面试题 HashMap,HashTable,ConcurrentHash的共同点和区别 HashMap HashTable ConcurrentHashMap ArrayList和Lin ...
- Java八股文纯享版——篇②:并发编程
注: 1.笔记为个人归纳整理,尽力保证准确性,如有错误,恳请指正 2.写文不易,转载请注明出处 3.本文首发地址 https://blog.leapmie.com/archives/c02a6ed1/ ...
随机推荐
- IE,FF等浏览器兼容性问题
_1:改变选中时候的背景色处理: ::selection { color:#fff; background-color: #5CB85C ;} ::-moz-selection { color:#ff ...
- js实现用按钮控制网页滚动、以及固定导航栏效果
实现效果如下: 页面内有三个按钮,分别控制页面向上.向下移动,以及暂停,并设置有导航栏,在滚动到某一位置时显示.且当用户主动控制鼠标滑轮时,滚动效果自动关闭.本页面只是演示如何实现,进行了简单的布局, ...
- Spring中声明式事务的几个属性的解释
声明式事务 @Transactional (通常用在service层)事务属性:传播行为,隔离级别,回滚,只读,过期 1,spring支持事务传播行为:propagation(常用以下两个) ① ...
- IT职业技能图谱:架构师、H5、DBA、移动、大数据、运维...
转载 作者:StuQ 文章收藏自微信:InfoQ 时隔近5个月,StuQ的小伙伴们再次出品了IT职业技能图谱更新版.这回除更新之前版本外,还添加了架构师.HTML 5.DBA等新的职业技能图谱.正 ...
- Note -「动态 DP」学习笔记
目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...
- FutureTask类的get方法如何实现线程同步等待
接上篇JDK中线程中实现同步等待闭环的一种方式 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com),为什么使用了FutureTask中的get方法就可以实现线程的同步等待?这就将重点讲述下F ...
- Spring Boot 自动配置(基本配置)
在关于Spring Boot的第一篇中就提到了其几大特性,其中关于Java配置简化属性注入在上一篇SpringBoot外部配置 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中已经介绍. ...
- 使用注解实现SpringIOC和SpringAOP
使用注解实现ioc @Component:实现Bean组件的定义 @Repository:标注dao类 @Service:标注业务类 @Controller:标注控制类 Bean的自动装配: @Aut ...
- jsp页面获取请求参数问题记录
同一个请求可以从请求路径中获取参数,使用param.参数名 window.location.href = "admin/page.html?pageNum="+pageNum+&q ...
- nginx加大缓存
http { server { listen 0.0.0.0:81; server_name localhost; -- proxy_buffer_size 128k; proxy_buffers 3 ...