jdk1.8 ConcurrentHashMap源码阅读

2 篇文章 0 订阅

put方法

  1. 如果没初始化则先初始化数组
  2. 如果数组对应位置上为null,则表示当前参数是数组该位置上的第一个节点,直接调用cas进行插入
  3. 如果数组对应位置上的节点hash值为MOVED,则表示正在进行扩容操作,调用helpTransfer方法帮助一起扩容
  4. 以上都不是则加锁进行节点的增加/更新操作
    • 如果头节点的hash值 >= 0,则表示是链表,循环遍历链表进行增加或删除操作,并累加binCount统计链表长度
    • 否则如果头结点是TreeBin,则表示该位置上的数据结构为红黑树,进行红黑树的增加或删除操作
  5. 判断binCount长度,如果达到阈值TREEIFY_THRESHOLD,则调用treeifyBin方法判断是需要扩容还是转换为红黑树
  6. 增加元素数量
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
  if (key == null || value == null) throw new NullPointerException();
  int hash = spread(key.hashCode());
  int binCount = 0;
  // 循环获取table是为了put操作如果没有抢占到锁或者cas失败不会导致put失败,会等到下一次继续执行
  for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; int n, i, fh;
    // 如果是第一次put,则初始化tab
    if (tab == null || (n = tab.length) == 0)
      tab = initTable();
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
      // 根据传入的key的hash获取tab中的元素赋值给f,如果等于null,则表示tab数组中该位置还没有任何元素,则通过cas添加元素
      if (casTabAt(tab, i, null,
                   new Node<K,V>(hash, key, value, null)))
        break;                   // no lock when adding to empty bin
    }
    // 如果获取到的元素f的hash值等于MOVED,则表示该元素已经在扩容的时候被转移了,那么就调用helpTransfer方法帮助扩容
    else if ((fh = f.hash) == MOVED)
      tab = helpTransfer(tab, f);
    else {
      // 以上都不是,则表示上面根据key的hash值从tab中获取到的f元素是链表或者树
      // 则需要对获取到的f元素加锁进行真正的添加Or更新操作
      V oldVal = null;
      synchronized (f) {
        // 再次判断获取到的f元素是否等于key当前运算之后得到的位置,防止其它线程已经将该元素移动
        if (tabAt(tab, i) == f) {
          // 如果fh大于等于0,则表示是链表的首元素,进行链表元素的增加或者更新操作
          if (fh >= 0) {
            // binCount记录元素的个数
            binCount = 1;
            // 循环遍历链表
            for (Node<K,V> e = f;; ++binCount) {
              K ek;
              // 如果找到key相等的值,则替换该元素的value进行更新
              if (e.hash == hash &&
                  ((ek = e.key) == key ||
                   (ek != null && key.equals(ek)))) {
                oldVal = e.val;
                if (!onlyIfAbsent)
                  e.val = value;
                break;
              }
              Node<K,V> pred = e;
              // 如果一直遍历到链表末尾都没找到key相等的元素,则创建元素添加到链表末尾
              if ((e = e.next) == null) {
                pred.next = new Node<K,V>(hash, key,
                                          value, null);
                break;
              }
            }
          }
          // 如果fh不是大于等于0,再判断该元素是不是tab(i)位置上的树的根节点,如果是树的根节点,则进行树的增改操作
          // 树的根节点会用常量TREEBIN表示,该值为-2
          else if (f instanceof TreeBin) {
            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) {
        // 如果binCount,既链表长度大于等于阈值,则调用treeifyBin方法
        // 该方法内部会判断tab数组长度是否大于64,如果大于64,则会转换成红黑树,否则扩容table数组
        if (binCount >= TREEIFY_THRESHOLD)
          treeifyBin(tab, i);
        if (oldVal != null)
          return oldVal;
        break;
      }
    }
  }
  // put操作真正完成之后将count加1
  addCount(1L, binCount);
  return null;
}

初始化方法initTable

/**
 * Initializes table, using the size recorded in sizeCtl.
 */
private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  // 循环是为了cas失败之后继续进行cas抢占操作
  while ((tab = table) == null || tab.length == 0) {
    // 如果sizeCtl < 0 表示有其它线程正在进行初始化,让出cpu
    if ((sc = sizeCtl) < 0)
      Thread.yield(); // lost initialization race; just spin
    // cas操作将SIZECTL设置为-1表示当前线程正在初始化
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
      try {
        // 再判断一次防止其它线程已经初始化table
        if ((tab = table) == null || tab.length == 0) {
          // DEFAULT_CAPACITY默认为16,初始化数组长度为输入的或者默认的16
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          table = tab = nt;
          // sc = n * 0.75
          sc = n - (n >>> 2);
        }
      } finally {
        // 设置sizeCtl为阈值
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

treeifyBin方法

/**
 * Replaces all linked nodes in bin at given index unless table is
 * too small, in which case resizes instead.
 */
private final void treeifyBin(Node<K,V>[] tab, int index) {
  Node<K,V> b; int n, sc;
  if (tab != null) {
    // MIN_TREEIFY_CAPACITY为64,如果数组长度小于这个长度,则进行数组扩容
    if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
      tryPresize(n << 1);
    // 如果不小于MIN_TREEIFY_CAPACITY,并且该位置对应的节点是链表则转换成红黑树
    else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
      synchronized (b) {
        // 双重判定防止并发修改
        if (tabAt(tab, index) == b) {
          // hd为头节点,tl为尾节点
          TreeNode<K,V> hd = null, tl = null;
          // 遍历链表,将链表中的元素转换成TreeNode,转换后的结构还是链表
          for (Node<K,V> e = b; e != null; e = e.next) {
            TreeNode<K,V> p =
              new TreeNode<K,V>(e.hash, e.key, e.val,
                                null, null);
            if ((p.prev = tl) == null)
              hd = p;
            else
              tl.next = p;
            tl = p;
          }
          // TreeBin初始化方法是将hd为头节点的链表转换成红黑树
          // 再将转换之后的红黑树塞到数组中对应的位置中去
          setTabAt(tab, index, new TreeBin<K,V>(hd));
        }
      }
    }
  }
}

扩容方法tryPresize

要完全看懂tryPresize方法需要看懂transfer方法,后面会分析transfer方法

/**
* Tries to presize table to accommodate the given number of elements.
*
* @param size number of elements (doesn't need to be perfectly accurate)
*/
private final void tryPresize(int size) {
  // size已经是数组的2倍了,tableSizeFor是将传入的数向上取最近的2的n次方
  // 所以这里的c = 2 * size
  int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
  tableSizeFor(size + (size >>> 1) + 1);
  int sc;
  // (sc = sizeCtl) >= 0表示还未被初始化或还未扩容
  while ((sc = sizeCtl) >= 0) {
    Node<K,V>[] tab = table; int n;
    if (tab == null || (n = tab.length) == 0) {
      n = (sc > c) ? sc : c;
      if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
        try {
          if (table == tab) {
            @SuppressWarnings("unchecked")
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
            table = nt;
            sc = n - (n >>> 2);
          }
        } finally {
          sizeCtl = sc;
        }
      }
    }
    // c <= sc表示已经扩容完成,n >= MAXIMUM_CAPACITY表示已经达到最大容量无法扩容
    else if (c <= sc || n >= MAXIMUM_CAPACITY)
      break;
    else if (tab == table) {
      int rs = resizeStamp(n);
      // sc < 0表示正在扩容,那么获取新数组nextTable,帮助一起扩容
      if (sc < 0) {
        Node<K,V>[] nt;
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
          break;
        // 这里需要注意,调用transfer方法之前会将
        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
          transfer(tab, nt);
      }
      // 如果没有线程在做扩容操作,则当前线程做新数组的初始化并扩容,这里需要注意,第一个进行扩容的线程将SIZECTL设置为(rs << RESIZE_STAMP_SHIFT) + 2
      else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                   (rs << RESIZE_STAMP_SHIFT) + 2))
        transfer(tab, null);
    }
  }
}

transfer方法

  1. 先判断需不需要初始化新数组,如果需要则初始化,stride表示每个线程一次性负责迁移的数组节点个数,transferIndex表示下一个线程负责迁移的数组起始下标,从大到小递减,每次一个线程过来帮助扩容就会减少stride
  2. while循环内部保证线程内下标在transferIndex - 1 到 transferIndex - stride之间移动,还保证线程之间负责的下标以stride进行分割
  3. 每次分配一个数组中的位置则将advance标为true,表示当前位置的迁移工作已经分配,并将对应的位置赋值为fwd,表示该位置的数据已经被迁移
  4. 真正进行迁移的时候会判断是链表还是红黑树,链表则会将链表分成两个,一个迁移到新数组的原下标位置上,另一个分配到新数组新下标位置上。如果是红黑树还会判断拆分后的长度是不是达到阈值,如果没达到则会将红黑树退化成链表
/**
 * Moves and/or copies the nodes in each bin to new table. See
 * above for explanation.
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
  int n = tab.length, stride;
  // 这里的stride表示步长,即每个线程分配stride个数组节点进行扩容
  // NCPU表示cpu核心数,所以这里的意思是总共最多会有cpu核心数个线程共同进行transfer操作
  // 如果分配的stride小于最小值MIN_TRANSFER_STRIDE(即16),则stride = 16
  if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
    stride = MIN_TRANSFER_STRIDE; // subdivide range
  // 这里进行新数组的初始化操作,将两个类变量nextTable和transferIndex进行赋值,
  // 其实参数中传进来的nextTab就是这里赋值的nextTable传进来的,transferIndex初始化为原始数组的长度
  if (nextTab == null) {            // initiating
    try {
      @SuppressWarnings("unchecked")
      Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
      nextTab = nt;
    } catch (Throwable ex) {      // try to cope with OOME
      sizeCtl = Integer.MAX_VALUE;
      return;
    }
    nextTable = nextTab;
    transferIndex = n;
  }
  int nextn = nextTab.length;
  // 这里初始化一个ForwardingNode节点,用来替换掉当前数组位置tab[i]中的数据
  // 表示当前位置的数据正在进行数据迁移操作,上面get方法中就会判断节点的has是否等于MOVED,
  ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
  // advance表示当前数组位置上的数据是不是正在迁移或者说当前数组位置上的数据迁移工作有没有被分配
  boolean advance = true;
  // finishing表示整体数据有没有迁移完成
  boolean finishing = false; // to ensure sweep before committing nextTab
  // 循环进行数据的迁移
  for (int i = 0, bound = 0;;) {
    Node<K,V> f; int fh;
    while (advance) {
      int nextIndex, nextBound;
      // 这个判断表示在每一个步长(stride)中将i进行递减,直到该步长用完,就会在第二个判断中判断总长度是否递减为0
      if (--i >= bound || finishing)
        advance = false;
      // 如果该stride长度已经遍历完成,看transferIndex也就是下一次遍历的起始位置是不是为0,为0表示所有数据都已经迁移完成,将赋值为-1
      else if ((nextIndex = transferIndex) <= 0) {
        i = -1;
        advance = false;
      }
      // 如果还没全部遍历完成,则将transferIndex递减一个stride,bound = transferIndex-stride,i = transferIndex - 1,表示本线程开始迁移transferIndex-stride至transferIndex - 1之间的数据
      // 由于这里的transferIndex是volatile修饰的类变量,所以每次修改这个值其它线程都能看到
      // 下一个到达这里的线程就会负责transferIndex-2*stride至transferIndex - stride - 1之间的数据了
      else if (U.compareAndSwapInt
               (this, TRANSFERINDEX, nextIndex,
                nextBound = (nextIndex > stride ?
                             nextIndex - stride : 0))) {
        bound = nextBound;
        i = nextIndex - 1;
        advance = false;
      }
    }
    // 如果不在[0,n)之间,则表示数据迁移可能已经完成了
    if (i < 0 || i >= n || i + n >= nextn) {
      int sc;
      if (finishing) {
        nextTable = null;
        table = nextTab;
        sizeCtl = (n << 1) - (n >>> 1);
        return;
      }
      // 这里需要结合调用transfer方法的那块代码看,每次调用transfer之前都会将SIZECTL + 1,这里表示每个线程完成了迁移就将SIZECTL - 1
      if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
        // 第一次调用transfer的时候会将SIZECTL赋值为(resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2,所以这里判断SIZECTL是不是已经递减到原始值,如果是则表示所有线程都完成了数据迁移,退出整个方法
        if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
          return;
        finishing = advance = true;
        i = n; // recheck before commit
      }
    }
    // 如果没迁移完成,并且当前数组位置的值==null,则将当前位置塞入fwd,表示当前位置的数据正在迁移
    else if ((f = tabAt(tab, i)) == null)
      advance = casTabAt(tab, i, null, fwd);
    else if ((fh = f.hash) == MOVED)
      advance = true; // already processed
    else {
      synchronized (f) {
        // 双重校验
        if (tabAt(tab, i) == f) {
          // 这里的ln是转移到新数组原来下标上的链表,hn是转移到新数组新下标位置的链表
          Node<K,V> ln, hn;
          // fh >= 0表示tab[i]位置上的是个链表
          if (fh >= 0) {
            int runBit = fh & n;
            Node<K,V> lastRun = f;
            // 这段循环是为了找出 最后 连续的能分配到相同下标上的链表
            for (Node<K,V> p = f.next; p != null; p = p.next) {
              int b = p.hash & n;
              if (b != runBit) {
                runBit = b;
                lastRun = p;
              }
            }
            // node.hash & n == 0则表示node.hash & (2n - 1) < 16
            // 举个栗子:设n = 16即原数组长度为16,二进制表示为n = 0000....10000,2n - 1 = 0000....11111,那么node.hash & n == 0则该hash可以表示为****....0****
            
            // ******** ******** ******** ***0****	node.hash
            // 00000000 00000000 00000000 00010000 	n
            // 00000000 00000000 00000000 00000000 	= 0
            
            // ******** ******** ******** ***0**** 	node.hash
            // 00000000 00000000 00000000 00011111 	2n - 1
            // 00000000 00000000 00000000 0000**** 	< 16
            if (runBit == 0) {
              ln = lastRun;
              hn = null;
            }
            else {
              hn = lastRun;
              ln = null;
            }
            for (Node<K,V> p = f; p != lastRun; p = p.next) {
              int ph = p.hash; K pk = p.key; V pv = p.val;
              if ((ph & n) == 0)
                ln = new Node<K,V>(ph, pk, pv, ln);
              else
                hn = new Node<K,V>(ph, pk, pv, hn);
            }
            setTabAt(nextTab, i, ln);
            setTabAt(nextTab, i + n, hn);
            // 将源数组中的对应位置设置为fwd,表示该位置的数据已经迁移
            setTabAt(tab, i, fwd);
            advance = true;
          }
          // 如果是书节点则需要构建红黑树
          else if (f instanceof TreeBin) {
            TreeBin<K,V> t = (TreeBin<K,V>)f;
            TreeNode<K,V> lo = null, loTail = null;
            TreeNode<K,V> hi = null, hiTail = null;
            int lc = 0, hc = 0;
            // 遍历TreeNode链表构建两个新的TreeNode链表
            for (Node<K,V> e = t.first; e != null; e = e.next) {
              int h = e.hash;
              TreeNode<K,V> p = new TreeNode<K,V>
                (h, e.key, e.val, null, null);
              // (h & n) == 0表示该节点在新数组中的原始下标
              if ((h & n) == 0) {
                if ((p.prev = loTail) == null)
                  lo = p;
                else
                  loTail.next = p;
                loTail = p;
                ++lc;
              }
              // 否则就是新数组的新下标
              else {
                if ((p.prev = hiTail) == null)
                  hi = p;
                else
                  hiTail.next = p;
                hiTail = p;
                ++hc;
              }
            }
            // 判断两个新链表的长度是不是 <= UNTREEIFY_THRESHOLD,如果是则转换为Node链表,否则构建红黑树
            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
            (hc != 0) ? new TreeBin<K,V>(lo) : t;
            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
            (lc != 0) ? new TreeBin<K,V>(hi) : t;
            setTabAt(nextTab, i, ln);
            setTabAt(nextTab, i + n, hn);
            setTabAt(tab, i, fwd);
            advance = true;
          }
        }
      }
    }
  }
}

get方法

/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}.  (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  int h = spread(key.hashCode());
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (e = tabAt(tab, (n - 1) & h)) != null) {
    // 如果头节点是我们要找的则直接返回
    if ((eh = e.hash) == h) {
      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
        return e.val;
    }
    // eh < 0表示正在扩容或者是红黑树,则调用节点的find方法查找,各种节点会实现各自的find方法
    else if (eh < 0)
      return (p = e.find(h, key)) != null ? p.val : null;
    // 否则遍历链表找到节点
    while ((e = e.next) != null) {
      if (e.hash == h &&
          ((ek = e.key) == key || (ek != null && key.equals(ek))))
        return e.val;
    }
  }
  return null;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值