ConcurrentHashMap 的 size()方法是非线程安全的。也就是说,当有线程调用 put 方法在添加元素的时候,其他线程在调用 size()方法获取的元素个数和实际存储元素个数是不一致的。原因是 size()方法是一个非同步方法,put()方法和 size()方法并没有实现同步锁。
put()方法
put()方法的实现逻辑是:在 hash 表上添加或者修改某个元素,然后再对总的元素个数进行累加。
其中,线程的安全性仅仅局限在 hash 表数组粒度的锁同步,避免同一个节点出现数据竞争带来线程安全问题。(如图)数组元素个数的累加方式用到了两个方案:
- 当线程竞争不激烈的时候,直接用 cas 的方式对一个 long 类型的变量做原子递增。
- 当线程竞争比较激烈的时候,使用一个 CounterCell 数组,用分而治之的思想减少多线程竞争,从而实现元素个数的原子累加。
size()方法
size()方法的逻辑就是遍历 CounterCell 数组中的每个 value 值进行累加,再加上baseCount,汇总得到一个结果。所以很明显,size()方法得到的数据和真实数据必然是不一致的。因此从 size()方法本身来看,它的整个计算过程是线程安全的,因为这里用到了 CAS的方式解决了并发更新问题
总结
但是站在 ConcurrentHashMap 全局角度来看,put()方法和 size()方法之间的数据是不一致的,因此也就不是线程安全的。之所以不像 HashTable 那样,直接在方法级别加同步锁。在我看来有两个考虑点:
- 直接在 size()方法加锁,就会造成数据写入的并发冲突,对性能造成影响,当然有些朋友会说可以加读写锁,但是同样会造成 put 方法锁的范围扩大,性能影响极大!
- ConcurrentHashMap 并发集合中,对于 size()数量的一致性需求并不大,并发集合更多的是去保证数据存储的安全性。