您当前的位置: 首页 >  面试

面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?

发布时间:2020-10-27 23:00:18 ,浏览量:0

没啥深入实践的理论系同学,在使用并发工具时,总是认为把HashMap改为ConcurrentHashMap,就完美解决并发了呀。技术言论虽然自由,但面对魔鬼面试官时,我们更在乎的是,这样真的就正确了吗?

我们都知道ConcurrentHashMap是个线程安全的哈希表容器,但它仅保证提供的原子性读写操作线程安全。

让我们看一个案例: 某含若干初始元素的Map,现在打算再填入一些,准备由10个线程并发处理: 运行时会发现最后ConcurrentHashMap的总元素数不是1000!

这是为什么呢?哪里出错了,说好的线程安全的容器呢?

ConcurrentHashMap这容器本身,可以确保多个线程在put元素时,不会互相干扰,但无法确保线程T1看到还需要put 100个元素但还未put 时,线程T2就看不到容器中的元素数量。 你往这个容器put 100个元素的操作不是原子性的,在其它线程看来可能会有一瞬间,容器里有964个元素,还需要填36个元素。

ConcurrentHashMap对外提供能力的限制:

  • 使用不代表对其的多个操作之间的状态一致,是没有其他线程在操作它的。如果需要确保需要手动加锁
  • 诸如size、isEmpty和containsValue等聚合方法,在并发下可能会反映ConcurrentHashMap的中间状态。因此在并发情况下,这些方法的返回值只能用作参考,而不能用于流程控制。显然,利用size方法计算差异值,是一个流程控制
  • 诸如putAll这样的聚合方法也不能确保原子性,在putAll的过程中去获取数据可能会获取到部分数据
解决方案

整段逻辑加锁:

既然使用ConcurrentHashMap还要全程加锁,那还不如使用HashMap!不完全是这样。 ConcurrentHashMap提供了一些原子性的简单复合逻辑方法,用好这些方法就可以发挥其威力。这就引申出代码中常见的另一个问题:在使用一些类库提供的高级工具类时,开发人员可能还是按照旧的方式去使用这些新类,因为没有使用其真实特性,所以无法发挥其威力。

案例

使用Map统计Key出现次数:

  • 使用ConcurrentHashMap来统计,Key的范围是10
  • 使用最多10个并发,循环操作1000万次,每次操作累加随机的Key
  • 如果Key不存在的话,首次设置值为1。

show me code:

现在我们知道了,应该直接锁住Map,再:

  • 判断
  • 读取现在的累计值
  • +1
  • 保存累加后值

这段代码在功能上的确毫无没有问题,但却无法充分发挥ConcurrentHashMap的性能。

优化后代码:

  • ConcurrentHashMap的原子性方法computeIfAbsent做复合逻辑操作,判断K是否存在V,若不存在,则把Lambda运行后结果存入Map作为V,即新创建一个LongAdder对象,最后返回V 因为computeIfAbsent返回的V是LongAdder,是个线程安全的累加器,可直接调用其increment累加。

这样在确保线程安全的情况下达到极致性能,且代码行数骤减。

性能测试
  • 使用StopWatch测试两段代码的性能,最后的断言判断Map中元素的个数及所有V的和是否符合预期来校验代码正确性

  • 性能测试结果:

比使用锁性能提升至少5倍。

computeIfAbsent高性能之道

Java的Unsafe实现的CAS。 它在JVM层确保写入数据的原子性,比加锁效率高:

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) { return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v); } 

所以不要以为只要用了ConcurrentHashMap并发工具就是高性能的高并发程序。

使用computeIfAbsent、putIfAbsent
  • 当Key存在的时候,如果Value获取比较昂贵的话,putIfAbsent就白白浪费时间在获取这个昂贵的Value上(这个点特别注意)
  • Key不存在的时候,putIfAbsent返回null,小心空指针,而computeIfAbsent返回计算后的值
  • 当Key不存在的时候,putIfAbsent允许put null进去,而computeIfAbsent不能,之后进行containsKey查询是有区别的(当然了,此条针对HashMap,ConcurrentHashMap不允许put null value进去)

参考

  • https://zhuanlan.zhihu.com/p/201333611
关注
打赏
1688896170
查看更多评论

暂无认证

  • 0浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.3212s