关于ConcurrentHashMap的键值不为null的见解。

关于ConcurrentHashMap的键值不为null的见解。

1. 可以从源码层面看出ConcurrentHashMap设计的底层就是键值不能为null,否则直接报错。

  • put添加元素就会进行判断
    在这里插入图片描述
  • 使用containsValue也会有判断
    在这里插入图片描述
  • 因此从设计的角度可以看出,ConcurrentHashMap不允许key,value为null。

谈谈我的看法

  • 归根结底还是二义性的问题,意思是说,当key和value可以为null时,ConcurrentHashMap使用get()和contains()方法在高并发场景下会存在二义性(下面细说)。而HashMap不会的原因是HashMap是单线程的(这里指的单线程是说HashMap是设计用在单线程场景的,用在多线程环境下也会出现bug),get()为null的时候,可以通过containsKey()或者containsValue()来查看究竟是key为null,还是value为null,还是key不存在。
1. 关于ConcurrentHashMap的value不能为null,这样设计是为什么?
  1. 首先假设value可以为null。那么当一个线程使用get(key) == null的时候,这时候我们无法判断ConcurrentHashMap里面是key不存在导致返回的null还是value就是null而返回的null。
  2. 这时候如果我们想HashMap那样用contains(key)判断key存不存在,如果contains(key)返回true,说明key存在,只是value为null。如果contains(key)返回false,说明key不存在。
  3. 但是别忘了我们这是多线程的情况下。那么当线程是使用contains(key)操作前,有别的线程使用·put操作或者remove操作的的话,就会产生错误了,排查不了。
  4. 例如本来ConcurrentHashMap中线程A执行get(key) == null这个操作的key是不存在的。那么正常来说线程A使用contains(key)查询的时候,返回的就是false,这才是正确的返回。但是在A使用contains(key)操作前线程B使用了put(key,null)操作了,那么这时候Acontains(key)返回的就是true了。这是不是就发生了错误。本身ConcurrentHashMap不存在key的,线程A执行get(key)这个操作返回null时,真实的情况应该是map中没有这个key。但是由于多线程环境下B操作了,导致最终A自己验证的结果是存在这个key,结果为null。
  5. 因此, ConcurrentHashMap的value为null会出现这样的问题,在设计的时候就不允许value为null。
2. 关于ConcurrentHashMap的key不能为null,这样设计是为什么?
  1. 同样的当key可以为null的时候。假设ConcurrentHashMap中是不存在key为null(意思是,可以有key为null,但是当前时刻没有人往里面添加key=null的记录)
  2. 那么线程A执行了操作get(null)操作,意思是查询key=null对应的value是多少。这时候返回的是get(null)=null。
  3. 这时候我们不能判断究竟是key==null时,value=null。还是说map中没有key为null的记录。
  4. 那么线程A就使用containsKey(null)来判断究竟是不是true,如果是true,说明有key为null,那么之前的操作get(null)=null说明就是key=null,value=null。如果是false,说明不存在key为null的记录,这时候返回false才是符合我们场景的设计。
  5. 但是在A执行containsKey(null)之前。B执行了put(null,null)。这时候map中多了key=null,value=null的记录了。那么这时候A在B操作后执行containsKey(null)返回的就是true了。这和真实情况下应该返回的false完全相反。
  6. 这就是在多线程高并发的情况下ConcurrentHashMap允许key为null带来的问题。

ConcurrentHashMap 能保证复合操作的原子性吗?

  • 答案肯定是可以,不然为何叫线程安全呢?对吧
  • 原因有以下几个吧:(需要一定的并发基础)
  • ConcurrentHashMap的tables(即数组)是加了volatile关键字(基础内容,需要的自己了解下)了,在上面进行了修改对其他线程是可见,这样子多线程的读写下,当有线程进行读或者写的时候都会第一时间将数据刷新到主内存,确保其他线程可以见,确保其他线程都可以读到最新值。
  • ConcurrentHashMap的复合操作从不是方法上加锁,而是采用局部代码块加锁(synchronized)的方式和使用CAS的方式,CAS是使用了Unsafe类的native函数,底层CAS函数是一种系统原语(可以类比操作系统的原语,是不可分割,是一定会一起执行完的)。
  • 下面可以贴部分代码
  • 比如ConcurrentHashMap的put操作调用的putval操作里面也有cas来保证并发的安全性
  • 在这里插入图片描述
  • 这里的U就是Unsafe类,里面方法可以使用的是系统原语级别的调用
    这里的U就是Unsafe类,里面方法可以使用的是系统原语级别的调用
  • 还有一些复合操作,可以看到也是处处存在局部加锁和CAS操作的
    在这里插入图片描述
    在这里插入图片描述

欢迎提出问题,大家一起讨论学习

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值