CurrentHashMap设计

1.7 VS 1.8

JDK1.7中,采取的是 Segment分段锁(继承ReentrantLock分段锁锁来实现的,1.8采取的是Node+CAS+Synchronized实现线程安全,Node就是数组中存储的对象,Synchronized锁的对象是Node,CAS主要是作用于更新数据,这里就包括根据key的hash值找到对应的桶,如果桶还不存在,那么通过一个CAS操作来设置桶的第一个元素,失败的继续执行下面的逻辑即向桶中插入或更新。

为什么这么设计

  • 锁粒度降低了
    不采用 segment 而采用 node,锁住 node 来实现减小锁粒度。

  • 官方对synchronized进行了优化和升级,使得synchronized不那么“重”了
    如果是线程并发量不大的情况下,那么Synchronized因为自旋锁,偏向锁,轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比ReentrantLock高效

在大数据量的操作下,对基于API的ReentractLock进行操作会有更大的内存开销;

ConcurrentHashMap中的key和value可以为null吗?为什么?

不可以,因为源码中是这样判断的,进行put()操作的时候如果key为null或者value为null,会抛出NullPointerException空指针异常。主要是因为有二义性:

  • 这个key从来没有在map中映射过,也就是不存在这个key;
  • 这个key是真实存在的,只是在设置key的value值的时候,设置为null了;

加锁方法:size()方法、containsValue方法

这些方法都是会加锁判断的,举个例子,size方法中:

  • 首先对segments进行两次统计操作,如果两次的统计值相同,则说明这段时间没有线程来访问这个map,拿到的size大小是正确的,返回。
  • 如果前两次拿到的值不相同,第三次就先对每一个segment进行加锁,然后在整体遍历计算一次,最后释放锁。所以在并发度比较高的情况之下,不建议使用这个方法,会把所有的segment都加锁

这类方法都影响效率,特别是并发情况下的插入、修改等,isEmpty()方法,它是不加锁的,

1.7的重哈希

这里重哈希不会扩容Segment数组大小,因为Segment数组的扩容之后,所有Segment内的数组都需要重新rehash,肯定会涉及并发问题,而整个设计上,就没有更上层的锁,这个事情干不了,所以只是对重哈希 Segments对象中的HashEntry数组进行重哈希,ConcurrentHashMap初始化之后就不可更改segments数组大小,默认的初始化值为16,那么它的并发度也是16,如果初始化时给segments定义的太大,也会造成命中的效率下降,影响整体的性能,一般默认的也够用了。

1.8扩容操作

该ConcurrentHashMap的扩容操作可以允许多个线程并发执行,那么就要处理好任务的分配工作。每个线程获取一部分桶的迁移任务,如果当前线程的任务完成,查看是否还有未迁移的桶,若有则继续领取任务执行,若没有则退出。在退出时需要检查是否还有其他线程在参与迁移工作,如果有则自己什么也不做直接退出,如果没有了则执行最终的收尾工作。

  • sizeCtl
    记录在执行的线程数
  • 分配任务

参考博客

心心念念的ConcurrentHashMap八连问,你能答对几个?

并发容器之ConcurrentHashMap(JDK 1.8版本)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值