HashMap和ConcurrentHashMap

HashMap的初始化与扩容
  1. HashMap 默认容量是 16,默认的负载因子是 0.75f
  2. 构造方法只会初始化一个空的 Node 数组,真正的初始化过程是在 put 方法中的 putVal 里面调用的,主要核心方法是 resize 方法
  3. HashMap 的容量固定为 2的n 次方,也就是如果我们传入了初始化容量,HashMap 不会使用我们传入的容量,而是会帮我们计算与我们传进去的参数最接近(大于等于)的一个 2n 的值作为容量
  4. HashMap 内部会存在一个阈值(threshold),该值为容量和负载因子的乘积,当 HashMap 里面的容量大于等于阈值时,会触发扩容。
  5. 负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低(时间换空间)。反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高(空间换时间)。
  6. 因为扩容是一个拷贝数组的过程,比较耗费资源。建议在初始化的时候指定容量,才能最大限度的利用 HashMap 的性能。

ConCurrentHashMap

concurrentHashMap默认会初始化一个长度为16的数组,它的核心仍然是哈希表,当hash冲突比较多的时候,会造成链表长度过长的问题,这种情况下会使得concurrentHashMap中的一个数组元素的查询复杂度增加,所以在jdk1.8中引入了红黑树这样的数据结构,当数组长度大于64,并且链表的长度大于等于8的时候,单向链表就会转换成红黑树,另外,随着concurrentHashMap的一个动态扩容,一旦链表的长度小于8,红黑树会退化成单向链表
ConcurrentHashmap线程安全在jdk1.7版本是基于分段锁实现,在jdk1.8是基于CAS+synchronized实现。
concurrentHashMap的基本功能:concurrentHashMap本质上是一个hashmap,功能和hashmap是差不多的,但是concurrentHashMap在Hashmap的基础上提供了并发安全的一个实现,并发安全的主要实现是通过对于链表或红黑树的头节点去加锁,来保证数据更新的安全性。
concurrentHashMap在性能方面做优化,比如在扩容的时候引入了多线程并发扩容的实现,简单来说就是多个线程对原始数组进行分片,分片之后,每个线程去负责一个分片的数据迁移,从而提升了扩容过程中数据迁移的一个整体效率。

ConCurrentHashMap是如何保证线程安全的?

jdk1.7底层实现:在jdk1.7中它使用的是数组+链表的形式实现,而数组又分为大数组Segment和小数组HashEntry,
在这里插入图片描述
在这里插入图片描述
通过put方法来看jdk1.7中ConcurrentHashMap是如何保证线程安全的,Segment本身是基于ReentrantLock实现的加锁和释放锁的操作,这样就能保证多个线程,同时,访问ConcurrentHashMap时,同一时间只有一个线程能操作相应的节点,这样就保证了ConcurrentHashMap的线程安全了,也就是说ConcurrentHashMap的线程安全是建立在Segment加锁的基础上的,所以我们把它称之为分段锁。
如下图所示,jdk1.8实现,在这里插入图片描述
在jdk1.7中,ConcurrentHashMap虽然是线程安全的,但因为它的底层实现是数组+链表形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而jdk1.8则使用数组+链表/红黑树的方式优化了ConcurrentHashMap的实现,具体实现结构如上图。

链表升级为红黑树的规则

当链表长度大于8,并且数组长度大于64时,链表就会升级为红黑树结构,ConcurrentHashMap在jdk1.8虽然保留了Segment的定义,但这仅仅是为了保证序列化时的兼容性,不再有任何结构上的用处了。

JDK1.8ConcurrentHashMap线程安全的实现

在这里插入图片描述
在JDK1.8中ConcurrentHashMap使用的是CAS+volatile或synchronized的方式来保证线程安全的,它的核心实现源码如上图,从上述源码可以看出,在jdk1.8中,添加元素时首先会判断容器是否为空,如果为空则使用volatile加CAS来初始化,如果容器不为空则根据存储的元素计算该位置是否为空,如果为空则利用CAS设置该节点;如果不为空则使用synchronize加锁,遍历桶中的数据,替换或新增节点到桶中,最后再判断是否需要转换为红黑树,这样就能保证并发访问时的线程安全了。

我们把上述流程简化一下,我们可以简单地认为在jdk1.8中,ConcurrentHashMap是在头节点加锁来保证线程安全的,锁的粒度相比Segment来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提升了,而且jdk1.8使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的O(n)优化到了O(logn)的时间复杂度。

总结:
ConcurrentHashMap在jdk1.7时使用的是数组+链表的形式实现的,其中数组分两类,大数组Segment和小数组HashEntry,而加锁是通过给Segment添加RentrantLock锁来实现线程安全的,而JDK1.8中ConcurrentHashMap使用的是数组+链表/红黑树的方式实现的,它是通过CAS或synchronized来实现线程安全的并且它的锁粒度更小,查询性能也更高。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值