前言
在看本文之前,最好先百度一下一下几个方面,以下几个方面不在本文讨论范围内,但是会引用到
- Java8之前的HashMap
- Java8之后的HashMap
- Java8之前的ConcurrentHashMap
- Java8之后的ConcurrentHashMap
- 无锁CAS操作,Java UnSafe
- volatile、synchronized关键字
圈定并发点
之所以Java引入ConcurrentHashMap就是为了解决,多线程场景下,同时操作一个HashMap会产生数据异常的问题,常用的主要有以下几个操作
- put 向map添加数据
- resize map扩容
- addCount 计数
- replace 替换map里一个数据
- remove 移除map里的对象
- clear 清除map里的一个或全部对象
其实精炼一下,就是三点
- 增删改操作 这些都是对map的写操作,必然要考虑多线程问题
- 扩容操作 对存储数据结构的,整体拷贝复制,必然要考虑
- 计数操作 增删必然会影响计数
具体分析
增删改操作
ConcurrentHashMap在增删改上施加的线程安全机制是近似的,以下以Put方法为例
public void put(K key, V value){
- 判断K,V是否非空,ConcurrentHashMap要求KV不能为空
- 根据K计算Hash值
- Node数组死循环开始
- Node数组如果为空初始化这个数组,初始化中,若有线程进入这里Thread.yield(),让其等待,同时使用UnSafe.compareAndSwapInt保证在初始化数组时的多线程安全,这里的Node和Hashmap的Node主要区别在val和next是volatile的即保证线程可见性
- 初始万数组后或数组不为null,使用UnSafe.getObjectVolatile查询hash指向的数组位置是否为空,若为空通过UnSafe.compareAndSwapObject把这个KV组成的Node插入Node数组
- 如果上一步UnSafe的方式查询发现那个位置不为空有node,且这个hash值为负,则来到这里进入扩容处理
- 如果上一步的hash值为正,synchronized (f),f是hash值,这里对这个node对应的链表做查找,由于一上来就synchronized ,后面的操作就线程安全了,根据K在这个Node的链表上是否有一样的K如果有覆盖V,如果没有,在链表上增加一个Node存储,Jdk1.8以后可能还涉及到这个链表长度超过8,要转为红黑树,以及树的总结点数少于6时,重新转换为链表的操作
- Node数组死循环结束
- map计数器+1
}
说完Put方法的逻辑,能看出多个针对多线程操作的特殊操作,主要是利用,UnSafe提供底层方法,用CAS的方式,对数组进行查询、增加修改,CAS是与锁迥然不同的多线程处理方式,效率更高,同时还使用了常见synchronized,这个已经经过多次优化,性能不错的关键字。可自行查看remove,clear的方法,大致逻辑也是类似。