大家记得点赞、收藏加关注! 高手:大家好,我是Mic,一个没有才华只能靠颜值混饭吃的Java程序员。
一个工作了5年的粉丝,最近去蚂蚁面试,在第一面的时候,本问到了几个Java基础的问题。
其中有一个问题比较有意思,面试官问:“ConcurrentHashMap的size()方法是线程安全的吗?为什么?”
关于这个问题的高手回答,我整理到了一个10W字的面试文档里面,大家可以扫描文章尾端二维码领取。
下面看看高手对这个问题的回答
ConcurrentHashMap的size()方法是非线程安全的。
也就是说,当有线程调用put方法在添加元素的时候,其他线程在调用size()方法获取的元素个数
和实际存储元素个数是不一致的。
原因是size()方法是一个非同步方法,put()方法和size()方法并没有实现同步锁。
put()方法的实现逻辑是:在hash表上添加或者修改某个元素,然后再对总的元素个数进行累加。
其中,线程的安全性仅仅局限在hash表数组粒度的锁同步,避免同一个节点出现数据竞争带来线程安全问题。
数组元素个数的累加方式用到了两个方案:
-
当线程竞争不激烈的时候,直接用cas的方式对一个long类型的变量做原子递增。
-
当线程竞争比较激烈的时候,使用一个CounterCell数组,用分而治之的思想减少多线程竞争,从而实现元素个数的原子累加。
size()方法的逻辑就是遍历CounterCell数组中的每个value值进行累加,再加上baseCount,汇总得到一个结果。
所以很明显,size()方法得到的数据和真实数据必然是不一致的。
因此从size()方法本身来看,它的整个计算过程是线程安全的,因为这里用到了CAS的方式解决了并发更新问题。
但是站在ConcurrentHashMap全局角度来看,put()方法和size()方法之间的数据是不一致的,因此也就不是线程安全的。
之所以不像HashTable那样,直接在方法级别加同步锁。在我看来有两个考虑点。
-
直接在size()方法加锁,就会造成数据写入的并发冲突,对性能造成影响,当然有些朋友会说可以加读写锁,但是同样会造成put方法锁的范围扩大,性能影响极大!
-
ConcurrentHashMap并发集合中,对于size()数量的一致性需求并不大,并发集合更多的是去保证数据存储的安全性。
关于这个问题,网上很多文章写得都很片面。
导致大家在找资料学习的时候,很容易被这种不完整的理解误导。
而且,这个问题切入的角度还挺有意思的,有些同学可能没往这个方向思考过。
也导致无法很好的回答出来。
另外,我将所有Java面试系列制作成了完整的面试文档。它的便捷之处在于,可以通过检索的方式,找到你想要的面试题,目前已经更新180期,总计超过15W字!