赞
踩
ConcurrentHashMap 精华代码很多,前面分析了 helpTransfer 和 transfer 和 putVal 方法,今天来分析一下 addCount 方法,该方法会在 putVal 方法中调用。
该方法可以配合 size 方法一起查看,关于该方法,楼主也写了一篇文章分析过:并发编程 —— ConcurrentHashMap size 方法原理分析
具体代码如下:
- addCount(1L, binCount);
- return null;
- 复制代码
当插入结束的时候,会调用该方法,并传入一个 1 和 binCount 参数。从方法名字上,该方法应该是对哈希表的元素进行计数的。
一起来看看 addCount 是如何操作的。
源码加注释:
- // 从 putVal 传入的参数是 1, binCount,binCount 默认是0,只有 hash 冲突了才会大于 1.且他的大小是链表的长度(如果不是红黑数结构的话)。
- private final void addCount(long x, int check) {
- CounterCell[] as; long b, s;
- // 如果计数盒子不是空 或者
- // 如果修改 baseCount 失败
- if ((as = counterCells) != null ||
- !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
- CounterCell a; long v; int m;
- boolean uncontended = true;
- // 如果计数盒子是空(尚未出现并发)
- // 如果随机取余一个数组位置为空 或者
- // 修改这个槽位的变量失败(出现并发了)
- // 执行 fullAddCount 方法。并结束
- if (as == null || (m = as.length - 1) < 0 ||
- (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
- !(uncontended =
- U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
- fullAddCount(x, uncontended);
- return;
- }
- if (check <= 1)
- return;
- s = sumCount();
- }
- // 如果需要检查,检查是否需要扩容,在 putVal 方法调用时,默认就是要检查的。
- if (check >= 0) {
- Node<K,V>[] tab, nt; int n, sc;
- // 如果map.size() 大于 sizeCtl(达到扩容阈值需要扩容) 且
- // table 不是空;且 table 的长度小于 1 << 30。(可以扩容)
- while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
- (n = tab.length) < MAXIMUM_CAPACITY) {
- // 根据 length 得到一个标识
- int rs = resizeStamp(n);
- // 如果正在扩容
- if (sc < 0) {
- // 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
- // 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
- // 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
- // 如果 nextTable == null(结束扩容了)
- // 如果 transferIndex <= 0 (转移状态变化了)
- // 结束循环
- if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
- sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
- transferIndex <= 0)
- break;
- // 如果可以帮助扩容,那么将 sc 加 1. 表示多了一个线程在帮助扩容
- if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
- // 扩容
- transfer(tab, nt);
- }
- // 如果不在扩容,将 sc 更新:标识符左移 16 位 然后 + 2. 也就是变成一个负数。高 16 位是标识符,低 16 位初始是 2.
- else if (U.compareAndSwapInt(this, SIZECTL, sc,
- (rs << RESIZE_STAMP_SHIFT) + 2))
- // 更新 sizeCtl 为负数后,开始扩容。
- transfer(tab, null);
- s = sumCount();
- }
- }
- }
- 复制代码
方法加了注释以后,还是挺长的。
总结一下该方法的逻辑:
x 参数表示的此次需要对表中元素的个数加几。check 参数表示是否需要进行扩容检查,大于等于0 需要进行检查,而我们的 putVal 方法的 binCount 参数最小也是 0 ,因此,每次添加元素都会进行检查。(除非是覆盖操作)
总结下来看,addCount 方法做了 2 件事情:
对 table 的长度加一。无论是通过修改 baseCount,还是通过使用 CounterCell。当 CounterCell 被初始化了,就优先使用他,不再使用 baseCount。
检查是否需要扩容,或者是否正在扩容。如果需要扩容,就调用扩容方法,如果正在扩容,就帮助其扩容。
有几个要点注意:
第一次调用扩容方法前,sizeCtl 的低 16 位是加 2 的,不是加一。所以 sc == rs + 1 的判断是表示是否完成任务了。因为完成扩容后,sizeCtl == rs + 1。
扩容线程最大数量是 65535,是由于低 16 位的位数限制。
这里也是可以帮助扩容的,类似 helpTransfer 方法。
作者:莫那·鲁道
链接:https://juejin.im/post/5b001618f265da0b9d787b7a
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。