ConcurrentHashMap源码解读

本文详细解读了ConcurrentHashMap的put方法,包括检查key和value非空、计算hash值、自旋插入、初始化table、插入操作、处理已存在元素、以及节点记数的增加。在增加节点记数时,涉及到了CAS操作、分片计数数组counterCells的初始化和扩容,以及并发控制策略。
摘要由CSDN通过智能技术生成

CHM

put方法

1.检查key和value不为null

2.计算hash值

int hash = spread(key.hashCode());  // key.hashCode() = h ,(h ^ (h >>> 16)) & HASH_BITS

3.自旋插入

int binCount = 0; 
Node<K, V>[] tab = table //将全局table赋值tab   

4.初始化table

 if (tab == null || (n = tab.length) == 0)
        tab = initTable();
​
 private final Node<K, V>[] initTable() {
    Node<K, V>[] tab;
    int sc;
    while ((tab = table) == null || tab.length == 0) {
      if ((sc = sizeCtl) < 0)  //占位符标识,表示已经有线程正在进行初始化操作
        Thread.yield(); // lost initialization race; just spin  //放弃时间片,释放cpu
      //若没有线程正在进行初始化,则使用CAS操作将sizeCtl设置为-1,表示当前线程正在进行初始化
      else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 
        try {
          if ((tab = table) == null || tab.length == 0) {
              //DEFAULT_CAPACITY 默认容量16
            int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
            @SuppressWarnings("unchecked")
            Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n]; //创建一个容量为16的数组
            table = tab = nt;  //奖数组的地址赋值给table
            sc = n - (n >>> 2); //sc =12 ,当数组的容量达到12后会进行扩容
          }
        } finally {
          sizeCtl = sc; //当数组的容量达到12后会进行扩容
        }
        break;
      }
    }
    return tab;
  }

5.初始化完成就进行真正的插入操作

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//若tab[i]==null,则代表当前位置还没有值,则不使用锁而是直接进行CAS操作
     if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null))) break;  //操作成功则结束自旋,否则进行下一次循环         
}

6.当i位置已经存在元素时,需要进行尾插,返回历史value,没有历史值则返回null

else {
        V oldVal = null;
        synchronized (f) { //f = tabAt(tab, i = (n - 1) & hash))  f 为Tab[i]  加锁同步
          if (tabAt(tab, i) == f) { // tab[i]的位置还没有改变
            if (fh >= 0) { //节点正常,代表还是链表结构
              binCount = 1; //初始链表的长度为 1 tab[i]
              for (Node<K, V> e = f;; ++binCount) { //从头结点开始遍历
                K ek;
                  //int hash = spread(key.hashCode());
                  //是同一个节点或者key相同
                if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                  oldVal = e.val;
                  if (!onlyIfAbsent) e.val = value;//如果不是只在缺少的情况下put则会进行value的更新
                  break;
                }
                Node<K, V> pred = e; //开始向后遍历
                if ((e = e.next) == null) { //没有下一个节点,则直接链接到尾部
                  pred.next = new Node<K, V>(hash, key, value, null);
                  break;
                }
                //e = e.next  在向后遍历的过程中检查有没有key相同的节点,若存在相同的key的节点则替换value值,否则遍历到最后的位置进行尾部插入
              }
            } else if (f instanceof TreeBin) {  //当前的bucket已经转换为红黑树
              Node<K, V> p;
              binCount = 2;
              if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key, value)) != null) {//插入红黑树,替换或者新增两种情况
                oldVal = p.val;
                if (!onlyIfAbsent) p.val = value;
              }
            }
          }
        }
        if (binCount != 0) {
          if (binCount >= TREEIFY_THRESHOLD) 
              //当链表长度 >= 8 && tab.length>=64 ,转换成红黑树,否则进行扩容,--》扩容后16->32
              treeifyBin(tab, i);
          if (oldVal != null) return oldVal;//当oldVal!=null时代表修改,所以直接返回,不需要增加个数
          break;
        }
      }

7.增加节点记数

  1. CAS操作baseCount+1

  2. CAS操作分片计数数组a的value+1

  3. 进入fullAddCount(x, uncontended)计数

    1. counterCells数组==null,则初始化一个大小为2的数组,并且根据随机数h&1得到一个元素位置,该元素value=1

    1. 如果存在线程在操作cell,直接CAS操作baseCount+=1

    1. countCells已经初始化,元素下标 i=(n - 1) & h,如果该位置未初始化,则进行初始化该元素value=1

    1. 位置i已经初始化,CAS操作i位置的元素value+1

addCount(1L, binCount);  //binCount  当前链表的长度  需要判断加上当前元素后需不需要扩容或者树的转换
    //1.新增tab[i] ---> binCount = 0
    //2.存在一个节点 ---> binCount = 1
    //3.存在两个节点 ---> binCount = 2
​
 private final void addCount(long x, int check) {  //binCount 当前链表长度
        CounterCell[] as; //分片计数
        long b, s;
        if ((as = counterCells) != null || 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值