原文地址:https://cloud.tencent.com/developer/article/1509556java
推荐一篇 ConcurrentHashMap 和 HashMap 写的比较的的文章数组
jdk1.7分段锁的实现
和hashmap同样,在jdk1.7中ConcurrentHashMap的底层数据结构是数组加链表。和hashmap不一样的是ConcurrentHashMap中存放的数据是一段段的,即由多个Segment(段)组成的。每一个Segment中都有着相似于数组加链表的结构。数据结构
关于Segment
ConcurrentHashMap有3个参数:并发
- initialCapacity:初始总容量,默认16
- loadFactor:加载因子,默认0.75
- concurrencyLevel:并发级别,默认16
其中并发级别控制了Segment的个数,在一个ConcurrentHashMap建立后Segment的个数是不能变的,扩容过程过改变的是每一个Segment的大小。性能
关于分段锁
段Segment继承了重入锁ReentrantLock,有了锁的功能,每一个锁控制的是一段,当每一个Segment愈来愈大时,锁的粒度就变得有些大了。优化
- 分段锁的优点在于保证在操做不一样段 map 的时候能够并发执行,操做同段 map 的时候,进行锁的竞争和等待。这相对于直接对整个map同步synchronized是有优点的。
- 缺点在于分红不少段时会比较浪费内存空间(不连续,碎片化); 操做map时竞争同一个分段锁的几率很是小时,分段锁反而会形成更新等操做的长时间等待; 当某个段很大时,分段锁的性能会降低。
jdk1.8的map实现
和hashmap同样,jdk 1.8中ConcurrentHashmap采用的底层数据结构为数组+链表+红黑树的形式。数组能够扩容,链表能够转化为红黑树。spa
何时扩容?
- 当前容量超过阈值
- 当链表中元素个数超过默认设定(8个),当数组的大小还未超过64的时候,此时进行数组的扩容,若是超过则将链表转化成红黑树
何时链表转化为红黑树?
当数组大小已经超过64而且链表中的元素个数超过默认设定(8个)时,将链表转化为红黑树.net
ConcurrentHashMap的put操做代码以下:对象

把数组中的每一个元素当作一个桶。能够看到大部分都是CAS操做,加锁的部分是对桶的头节点进行加锁,锁粒度很小。blog
为何不用ReentrantLock而用synchronized ?
- 减小内存开销:若是使用ReentrantLock则须要节点继承AQS来得到同步支持,增长内存开销,而1.8中只有头节点须要进行同步。
- 内部优化:synchronized则是JVM直接支持的,JVM可以在运行时做出相应的优化措施:锁粗化、锁消除、锁自旋等等。
总结:
经过源码能够看出 使用 CAS + synchronized 方式时 加锁的对象是每一个链条的头结点,也就是 锁定 的是冲突的链表,因此再次提升了并发度,并发度等于链表的条数或者说 桶的数量。那为何sement 不把段的大小设置为一个桶呢,由于在粒度比较小的状况下,若是使用ReentrantLock则须要节点继承AQS来得到同步支持,增长内存开销,而1.8中只有头节点须要进行同步,粒度表较小,相对来讲内存开销就比较大。因此不把segment的大小设置为一个桶。
参考
- https://my.oschina.net/pingpangkuangmo/blog/817973
- https://www.wanaright.com/2018/09/30/java10-concurrenthashmap-no-segment-lock/
- https://blog.csdn.net/mian_csdn/article/details/70185104
- https://cloud.tencent.com/developer/article/1509556
原文地址:https://cloud.tencent.com/developer/article/1509556
若有侵权,留言删除