1. 小声哔哔
今天来嗨一下JDK1.8的ConcurrentHashMap,JDK1.8和JDK1.7的ConcurrentHashMap最大的区别就是JDK1.8去除了segment,直接使用Node数组加链表的数据结构,而且链表过长会转换为红黑树,其中链表转红黑树的阀值是在一个链表挂的个数超过8个的时候会转换为红黑树。
JDK1.8版本的ConcurrentHashMap有6000+代码,所以我只看常用的,对红黑树的处理就领会精神吧。
1.1. 主要参数及内部类
- 内部类:Node类用户存放实际的key和value值
- sizeCtl:
负数:表示进行初始化或者扩容,-1表示正在初始化,-N,表示有N-1个线程正在进行扩容
正数:0 表示还没有被初始化,>0的数,初始化或者是下一次进行扩容的阈值
- TreeNode 用在红黑树,表示树的节点,TreeBin是实际放在table数组中的,代表了这个红黑树的根。
2. 主要方法
查看源码时发现构造方法中什么也没做,这里就不贴代码了。
- put方法
主要是调用putVal方法
可以看到首先会调用key的hashcode方法获取key的hashcode方法,然后调用spread方法进行一次再散列。
HASH_BITS值为0x7fffffff,二进制为31个1
可以看出spread方法中将key的hash值保留高16位,然后与上HASH_BITS保证最终结果是正数。
回到put方法,获取到最终hash值后,判断table是否为空,若为空则初始化table
可以看到初始化table时,首先判断sizeCtl是否为负数,若为负数则说明有别的线程在进行初始化,调用yield方法释放时间片,若不小于0则使用CAS指令设置sizeCtl为-1,设置成功后初始化Node数组,默认大小为16,同时需要注意到设置sc的时候首先减去了n的四分之一,也就是我们熟悉的阀值0.75。
回到put方法
这里获取了table该位置上的元素,若为空则直接设置node值
继续往下看,这里使用了synchronized关键字来保证并发安全,还记得之前看JDK1.7版本的时候是使用segment继承ReentrantLock可重入锁来保证并发安全。
根据代码逻辑,若当前Node数组位置上的元素的key与传入的key值相同且hash相同,则替换当前节点的value值,若不相同且当前节点是尾节点则根据传入的key和value创建一个node出来挂在链表的最尾部。
部分1的代码逻辑就是若当前节点是树节点则做红黑树相关操作,这里限于能力就不细讲了。
部分2的代码就是校验当前链表长度饰扣大于阀值,这里的阀值就是TREEIFY_THRESHOLD,也就是我们之前说的链表长度超过8的时候就转化为红黑树。
- get方法
可以看到get方法就简单了许多,还是调用spread方法进行再散列获取再散列后的hash值,然后根据我截图中的三种情况进行查找。