本文已参与「新人创作礼」活动,一起开启掘金创作之路。
ConcurrentHashMap是面试常客了。
Concurrent 类型的容器
- 内部很多操作使用 cas 优化,一般可以提供较高吞吐量
- 弱一致性
- 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,但是旧数据
- 求大小弱一致性,size 操作未必是 100% 准确
为什么会有ConcurrentHashMap
因为HashMap是线程不安全的,而使用 HashTable 虽然可以保证线程安全问题,但其是采用的是 synchronized 锁将整个 HashTable 中的数组锁住, 在多个线程中只允许一个线程访问 Put 或者 Get,效率低。
而且多线程下HashMap有两个问题:
- 循环死链问题:JDK1.7之前头插法导致的,后面1.8改为尾插法了。
- 值覆盖问题:多线程计算索引是同一个位置,然后前一个的put操作被后一个put操作给覆盖了
于是多线程的情况下 JDK 官方推荐使用 ConcurrentHashMap
ConcurrentHashMap和HashTable的区别
实现线程安全的方式(重要):
① 在 JDK1.7 的时候,ConcurrentHashMap采用分段锁设计,对整个桶数组进行了分段设计,切分为多个segment,再把小数组分为多个hashEntry。 有种从空间上细化锁粒度的感觉
到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,通过使用 synchronized 和 CAS 来完成并发控制。
② Hashtable:只是采用 数组+链表 的形式,都是在方法上使用 synchronized 来保证线程安全,效率非常低下。
ConcurrentHashMap 不支持 key 或者 value 为 null 的原因?
因为多线程操作下,会有二义性。
二义性是指,不确定是key为null还是value为null
单线程的hashmap可以通过containsKey做判断,而多线程下的concurrentHashmap可能会同时发生put操作,所以不支持key为null