在ConcurrentHashMap中是对数组中的某一个下标上的元素加锁(synchronized),如果在这个下标上的元素是链表,那么就锁链表的头节点,如果是红黑树,那么就锁红黑树的对象,红黑树的整个树结构都是封装在TreeBin这个对象中的,在这个对象中保存了root属性记录根节点,每个节点也是由TreeNode组成,把整个红黑树封装为一个对象时为了加锁,因为在红黑树插入元素时会出现旋转,有可能造成root节点的变化,如果把root节点直接放到数组的中,此时在这个root元素上加锁,那么一旦红黑树进行旋转,那么root节点发生变化,后面的线程会在新的root节点上面加锁,与之前线程在老root节点加的锁无法互斥。

一、put()方法

在元素put到数组时,会判断当前下标位置的元素是链表还是红黑树,红黑树保存在TreeBin对象中,hash值固定为-2,如果判断当前下标位置是红黑树,那么会进入插入到树的逻辑分支。

ConcurrentHashMap-put(一).png二、链表的树化

ConcurrentHashMap的树化和HashMap有些不同,首先需要将节点封装到TreeBin对象中。锁的范围仅在元素插入链表或红黑树的过程中,之后就会释放,链表转红黑树的逻辑并不在这个锁的范围里,因为在树化的逻辑中会重新加锁。
ConcurrentHashMap-put-treeifyBin.png

三、addCount()方法

如果链表当中存在key相同的节点,那么会直接覆盖然后返回旧值,就直接返回旧值,如果新的节点插入到链表或者红黑树中,那么就会对ConcurrentHashMap的元素个数进行累加,因为ConcurrentHashMap是线程安全的,也就是说有可能在多个线程的情况下同时累加操作。
在ConcurrentHashMap中,计数是使用 long baseCount 和 CounterCell[] counterCells 来统计的,线程统计会先在baseCount上操作增加,当多个线程统计需要增加数量的时候,会分配到 counterCells 数组的不同槽位中进行自增操作,类似于 LongAdder 的逻辑。
size() 方法调用了sumCount()方法,在这个方法中是在每个counterCell的value累计再加上baseCount后得到的最终结果。

  1. public int size() {
  2. long n = sumCount();
  3. return ((n < 0L) ? 0 :
  4. (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
  5. (int)n);
  6. }
  7. final long sumCount() {
  8. CounterCell[] as = counterCells; CounterCell a;
  9. long sum = baseCount;
  10. if (as != null) {
  11. for (int i = 0; i < as.length; ++i) {
  12. if ((a = as[i]) != null)
  13. sum += a.value;
  14. }
  15. }
  16. return sum;
  17. }
  1. addCount() 前半部分逻辑,如果线程在槽位上修改数量成功了,没有进入槽位数组的扩容逻辑,那么会计算出了整个map中的元素,调用了sumCount()方法,之前的逻辑已经计算出了扩容的阈值,后半部分逻辑会根据当前map的元素个数来判断的。<br />![ConcurrentHashMap-put-addCount(一).png](https://cdn.nlark.com/yuque/0/2022/png/21820542/1645630429121-c1a6f126-087d-4926-83d5-7eac1d3624bc.png#clientId=uc3f1646b-9708-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u2b811b57&margin=%5Bobject%20Object%5D&name=ConcurrentHashMap-put-addCount%EF%BC%88%E4%B8%80%EF%BC%89.png&originHeight=8768&originWidth=6200&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2285714&status=done&style=none&taskId=u92f22431-e529-4de5-b769-d0ffbd61807&title=) addCount() 后半部分逻辑和扩容有关,在前半部分计算出了当前map中的元素总数,接下来会判断是否达到了扩容阈值。<br />首先会判断变量check是否大于等于0,这个变量是上级方法传进来的binCount,如果是put方法,调用的addCount()方法,那么这个值是不会小于0的,如果是remove()方法调用addCount(-1L, -1),那么就会是负数表示删除了一个元素,删除是不需要扩容的。<br />ConcurrentHashMap的扩容原理是,首先创建一个比原来数组大一倍的新数组,然后确定每个线程的步长,从数组的末尾开始,到步长为止(从后往前数),数组上的这些元素都由一个线程来负责移动到新的数组上面。当移动某一个下标位置的元素时,会创建一个对象,hash值是-1(即MOVED),这样如果有其他线程想put到这个下标位置时,就会判断出这个数组正在扩容,从而帮助一起去移动元素,直到所有的元素都被转移完毕,put的线程就会进入下次循环并读取到最新的数组,重新put。
  1. // 在put方法中有这样一个分支
  2. // 这个helpTransfer方法最终也会调用transfer(tab, nextTab)方法
  3. else if ((fh = f.hash) == MOVED)
  4. tab = helpTransfer(tab, f);

ConcurrentHashMap-put-addCount(二).png