Java基础之jdk8 ConcurrentHashMap源码解读

一、存储结构的演变

jdk 1.7采用分段锁技术,整个Hash表被分成多个段,每个段中会对应一个Segment段锁,段与段之间可以并发访问,但是多线程想要操作同一个段是需要获取锁的。所有的 put、get和remove 等方法都是根据键的hash值对应到相应的段中,然后尝试获取锁进行访问。
在这里插入图片描述
jdk 1.8取消了基于Segment的分段锁思想,改用CAS + synchronized控制并发操作,在某些方面提升了性能。并且追随1.8版本的HashMap底层实现,使用数组+链表+红黑树进行数据存储。
在这里插入图片描述

二、源码成员变量

2.1 table

代表整个哈希表

transient volatile Node<K,V>[] table;

2.2 nextTable

用于哈希表扩容,扩容完成后会被重置为null。

private transient volatile Node<K,V>[] nextTable;

2.3 baseCount和counterCells

baseCount和counterCells一起保存着整个哈希表中存储的所有的结点的个数总和。

private transient volatile long baseCount;

private transient volatile CounterCell[] counterCells;

2.4 sizeCtl

这是一个非常重要的属性,无论是初始化哈希表,还是扩容 rehash 的过程,都是需要依赖这个关键属性的。该属性有以下几种取值:

  • sizeCtl=0,代表数组未被初始化,默认初始容量为16;
  • sizeCtl>0,如果数组未初始化,那么记录数组的初始容量。如果数组已初始化,那么其记录数组的扩容阈值(数组的初始容量 * 0.75);
  • sizeCtl=-1,表示是数组正在解析初始化;
  • sizeCtl<0 && sizeCtl !=-1,表述数组正在扩容,-(1+n)表示此事有n个线程正在共同完成数组的扩容操作。
private transient volatile int sizeCtl;

三、辅助函数

3.1 哈希值计算方法

static final int HASH_BITS = 0x7fffffff;

static final int spread(int h) {
    // 位与HASH_BITS,表示生成的hash值必定时正数
    return (h ^ (h >>> 16)) & HASH_BITS;
}

3.2 最小幂次数计算方法

计算大于指定值最小的“2的幂指数”

private static final int tableSizeFor(int c) {
    // 防止cap已经是2的幂。如果cap已经是2的幂,又没有这个减1操作,
    // 则执行完后面的几条无符号操作之后,返回的 apacity将是这个cap的2倍。
    int n = c - 1;
    n |= n >>> 1;  // 最高位非零1位右移1位,逻辑或n,此时可得最高2位为11
    n |= n >>> 2;  // 最高位非零2位右移2位,逻辑或n,此时可得最高2位为1111
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

四、构造函数

4.1 默认参数

默认容量为DEFAULT_CAPACITY=16

public ConcurrentHashMap() {

}

4.2 指定初始容量

指定初始容量构造器,初始化会计划一个初始容量数:大于该值并且是2的幂次数。
推导公式
假定需要指定容器容量为n(容量值满足最小2的幂指数),那么初始值计算公式为:initialCapacity = (n - 1) / (2 * 0.75)

public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? // 判断指定初始容量是否大于最大容量值的一半
               MAXIMUM_CAPACITY :
               // 重新指定初始值为: 2 * 0.75 * initialCapacity   + 1
               // 可以推导出:如果需要容量为n,那么初始值initialCapacity= (n - 1) / (2 * 0.75)
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

4.3 指定初始容量和加载因子

加载因子是根据泊松方程统计所得,如果设置不合适会导致频繁扩容,不建议使用该方法

 public ConcurrentHashMap(int initialCapacity, float loadFactor) {
     this(initialCapacity, loadFactor, 1);
 }

4.4 指定初始容量、加载因子和并发线程数

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel)   // Use at least as many bins
        initialCapacity = concurrencyLevel;   // as estimated threads
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    this.sizeCtl = cap;
}

五、数据变更操作

5.1 结构初始化

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();
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
    		// 使用unsafe原子操作,假如等于sc,则将sizeCtl设置为-1,表示正在初始化
   			// compareAndSwapInt可以避免多个线程同时对表格初始化,如果有其他线程
    		// 已经重置为-1,那么当前的线程就不需要对表格进行初始化操作。
            try {
                // 二次确认当前还没被初始化
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // 全局变量LOAD_FACTOR = 0.75f, 表示负载阈值
                    // 计算负载阈值sc = n * 0.75, 采用以下移位操作更快
                    sc = n - (n >>> 2); 
                }
            } finally {
                // 初始化之后, 更新为负载阈值, 超过会重新扩容
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

5.2 数据插入

/**
 * 数据插入
 * @param key 键
 * @param value 值
 * @param onlyIfAbsent 只有缺失时才插入,否则强制更新
 */
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;
   for (Node<K,V>[] tab = table;;) {
       Node<K,V> f; int n, i, fh;
       // 如果没有初始化,或者数组长度为零
       if (tab == null || (n = tab.length) == 0)
           tab = initTable();
       // 判断索引位置是否有存在元素
       else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
 			// tabAt原子操作f = tab[i], 找到表格中对应的桶, i为hash投影的索引
           if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
           		// casTabAt原子操作,主要是tab[i]= new Node(...),如果失败则表示
           		// 其他线程已经设置,就for中可以直接获取
               break;
       }
       // 如果桶结点为ForwardingNode,则协助扩容
       else if ((fh = f.hash) == MOVED)
           // 协助扩容
           tab = helpTransfer(tab, f);
       else {
           V oldVal = null;
           // 普通的节点, 将头锁住方便更新
           synchronized (f) {
           	    // 再次检测下,如果不等说明被其他线程更新,等待一次重新操作
               if (tabAt(tab, i) == f) { 
                   if (fh >= 0) {
                   	   // 如果是正常节点, 直接往后查询
                   	   // binCount表示当前桶中节点的数量
                       binCount = 1;
                       for (Node<K,V> e = f;; ++binCount) {
                           K ek;
                           if (e.hash == hash &&
                               ((ek = e.key) == key ||
                                (ek != null && key.equals(ek)))) {
                               oldVal = e.val;
                               // 如果已经找到, 则根据onlyIfAbsent是否更新
                               if (!onlyIfAbsent)
                                   e.val = value;
                               break;
                           }
                           Node<K,V> pred = e;
                           // 假如没找到则追加到链尾
                           if ((e = e.next) == null) {
                               pred.next = new Node<K,V>(hash, key,
                                                         value, null);
                               break;
                           }
                       }
                   }
                   // 当前的桶是一个红黑二叉树的头结点
                   else if (f instanceof TreeBin) {
                       Node<K,V> p;
                       binCount = 2; //  binCount设置为2, 防止转化成二叉树
                       if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                      value)) != null) {
                           oldVal = p.val;
                           if (!onlyIfAbsent)
                               p.val = value;
                       }
                   }
               }
           }
           if (binCount != 0) {
               // 假如当前桶的节点数量大于8个,那就该桶的数据转成红黑二叉树形式
               // 注意:treeifyBin会检查元素个数是否大于MIN_TREEIFY_CAPACITY=64
               if (binCount >= TREEIFY_THRESHOLD)
                   treeifyBin(tab, i);
               if (oldVal != null)
                   return oldVal;
               break;
           }
       }
   }
   // 元素个数增加1, binCount表示当前插入数据所在桶元素序号(1,2...), 根据条件决定是否扩容
   addCount(1L, binCount);
   return null;
}

5.3 树结点更新

putTreeVal表示查找二叉树上对应的节点,如果没找到则自动生成一个节点,具体的红黑二叉树的旋转之类的就不再赘述了,网上太多资料。

final TreeNode<K,V> putTreeVal(int h, K k, V v) {
  Class<?> kc = null;
  boolean searched = false;
  for (TreeNode<K,V> p = root;;) {
      int dir, ph; K pk;
      if (p == null) {
          // 当前桶为空,则直接创建一个新的节点
          first = root = new TreeNode<K,V>(h, k, v, null, null);
          break;
      }
      else if ((ph = p.hash) > h)
          // hash值大于当前key的,在左子树
          dir = -1;
      else if (ph < h)
          // hash值小于当前key的,在右子树
          dir = 1;
      else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
          // 等于,则比较key是否相等
          return p;
      else if ((kc == null &&
                (kc = comparableClassFor(k)) == null) ||
               (dir = compareComparables(kc, k, pk)) == 0) {
        // 如果hash值相等,那么就比较下是否是String或者实现了Comparable接口
        // 如果等出现二次碰撞,则左右子树都搜索
          if (!searched) {
              TreeNode<K,V> q, ch;
              searched = true;
              // 找到对应的节点
              if (((ch = p.left) != null &&
                   (q = ch.findTreeNode(h, k, kc)) != null) ||
                  ((ch = p.right) != null &&
                   (q = ch.findTreeNode(h, k, kc)) != null))
                  // 找到直接返回即可
                  return q;
          }
          // 假如没有找到,则根据类名、System.identityHashCode比较来确定到底放在左右哪边
          dir = tieBreakOrder(k, pk);
      }

      TreeNode<K,V> xp = p;
      // 根据比较的大小dir来确定插入左子树还是右子树
      if ((p = (dir <= 0) ? p.left : p.right) == null) {
          TreeNode<K,V> x, f = first;
          first = x = new TreeNode<K,V>(h, k, v, f, xp);
          if (f != null)
              f.prev = x;
          if (dir <= 0) // <=0 左子树
              xp.left = x;
          else // >0 右子树
              xp.right = x;
          if (!xp.red)
              x.red = true;
          else {
              // 加锁,调整平衡
              lockRoot();
              try {
                  root = balanceInsertion(root, x);
              } finally {
                  unlockRoot();
              }
          }
          break;
      }
  }
  assert checkInvariants(root);
  return null;
}

// 多次冲突碰撞之后,就用此来决定谁大谁小,以区分在左还是在右子树上
static int tieBreakOrder(Object a, Object b) {
  int d;
  if (a == null || b == null ||
      (d = a.getClass().getName().
       compareTo(b.getClass().getName())) == 0)
      d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
           -1 : 1);
  return d;
}

5.4 更新结点数

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // counterCells和baseCount用于存储结点数
    // 这里可以看到首先对baseCount进行更新,其次是counterCells
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        // ThreadLocalRandom.getProbe()值对线程是固定的
        // 分析可得:第一次对baseCount添加失败, 则这里会对counterCells进行更新
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            // 多线程修改baseCount时, 
            // 竞争失败的线程会执行fullAddCount(x, uncontended), 把x的值插入到counterCell类中
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        // 计算节点数
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        // s为更新之后的结点数, 判断是否大于sizeCtl阈值
        // 并且, table不为空
        // 以及, 桶长度小于最大容许范围
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            // 从上面可得sc = sizeCtl
            // 根据sizeCtl值知道, sizeCtl<0表示正在扩容
            if (sc < 0) {
            	// 判断扩容是否完成
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                // 更新sizeCtl值, 协助扩容
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 开始扩容
            // 可以结合transfer、resizeStamp、helpTransfer查看扩容对sizeCtl的操作
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
          		// 将sizeCtrl设置负数, 然后开始转移
                transfer(tab, null);
            s = sumCount();
        }
    }
}

5.5 修改计数

多线程修改baseCount时,竞争失败的线程会执行fullAddCount(x, uncontended),把x的值插入到counterCell类中

private final void fullAddCount(long x, boolean wasUncontended) {
    int h;
    //获取当前线程的probe的值, 如果值为0, 则初始化当前线程的probe的值, probe就是随机数
    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();      // force initialization
        h = ThreadLocalRandom.getProbe();
        // 由于重新生成了probe,未冲突标志位设置为true
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty

    //自旋
    for (;;) {
        CounterCell[] as; CounterCell a; int n; long v;
        //说明counterCells已经被初始化过了, 我们先跳过这个代码, 先看初始化部分
        if ((as = counterCells) != null && (n = as.length) > 0) {
            // 通过该值与当前线程probe求与,获得cells的下标元素,和hash 表获取索引是一样的
            if ((a = as[(n - 1) & h]) == null) {
                //cellsBusy=0, 表示counterCells不在初始化或者扩容状态下
                if (cellsBusy == 0) {            // Try to attach new Cell
                    //构造一个CounterCell的值,传入元素个数
                    CounterCell r = new CounterCell(x); // Optimistic create
                    //通过cas设置cellsBusy标识,防止其他线程来对counterCells并发处理
                    if (cellsBusy == 0 &&
                        U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                        boolean created = false;
                        try {               // Recheck under lock
                            CounterCell[] rs; int m, j;
                            //将初始化的r对象的元素个数放在对应下标的位置
                            if ((rs = counterCells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            //恢复标志位
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        //说明指定cells下标位置的数据不为空,则进行下一次循环
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            //说明在addCount方法中cas失败了,并且获取probe的值不为空
            else if (!wasUncontended)       // CAS already known to fail
                //设置为未冲突标识,进入下一次自旋
                wasUncontended = true;      // Continue after rehash
            //由于指定下标位置的cell值不为空,则直接通过cas进行原子累加,如果成功,则直接退出
            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                break;
            // 如果已经有其他线程建立了新的counterCells,
            // 或者CounterCells大于CPU核心数(很巧妙,线程的并发数不会超过cpu核心数)
            else if (counterCells != as || n >= NCPU)
                //设置当前线程的循环失败不进行扩容
                collide = false;            // At max size or stale
            //恢复collide状态,标识下次循环会进行扩容
            else if (!collide)
                collide = true;
            //进入这个步骤,说明CounterCell数组容量不够,线程竞争较大,所以先设置一个标识表示为正在扩容
            else if (cellsBusy == 0 &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                try {
                    if (counterCells == as) {// Expand table unless stale
                        //扩容一倍, 这个扩容比较简单
                        CounterCell[] rs = new CounterCell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        counterCells = rs;
                    }
                } finally {
                    //恢复标识
                    cellsBusy = 0;
                }
                collide = false;
                //继续下一次自旋
                continue;                   // Retry with expanded table
            }
            //继续下一次自旋
            h = ThreadLocalRandom.advanceProbe(h);
        }
        //cellsBusy=0表示没有在做初始化, 
        // 通过cas更新cellsbusy的值标注当前线程正在做初始化操作
        else if (cellsBusy == 0 && counterCells == as &&
                 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            boolean init = false;
            try {  // Initialize table
                if (counterCells == as) {
                    //初始化容量为2
                    CounterCell[] rs = new CounterCell[2];
                    //将x也就是元素的个数 放在指定的数组下标位置
                    rs[h & 1] = new CounterCell(x);
                    //赋值给counterCells
                    counterCells = rs;
                    //设置初始化完成标识
                    init = true;
                }
            } finally {
                //恢复标识
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            //竞争激烈,其它线程占据cell 数组,直接累加在base变量中
            break;                          // Fall back on using base
    }
}

5.6 桶扩容

/**
 * 桶扩容
 * @param tab 原桶
 * @param nextTab 协助扩容桶
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        // 将length/8, 然后除以CPU核数。如果得到的结果小于16, 那么就使用16。
        // 目的:让每个CPU处理的桶一样多, 避免出现转移任务不均匀的现象。
        // 如果桶较少的话, 默认一个 CPU(一个线程)处理 16 个桶
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                // 扩容2倍
                @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;
        // 创建一个fwd节点, 用于占位。
        // 当其他线程发现这个槽位中是fwd类型的节点, 则跳过这个节点。
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        // 用来标记是否完成,方便提交更改
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            // 先获取锁定一下转移数据表的索引范围值bound ~ i
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing) // 不停往前移动表索引
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    // 如果是<= 0,表明转移全部完成
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                	// transferIndex减去步伐stride,下个线程就从新的索引处nextBound转移数据
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    // 将新表替换旧表
                    nextTable = null;
                    table = nextTab;
                    // 负载阈值:2 * 0.75 * n 
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    // 当前的线程转移完成
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) 
                    // 判断是否还有其他的线程在转移
                        return;
                    // 转移完成
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                // 如原来的表没有数据,则直接设置一个转移节点到旧表中
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                // 有可能其他的线程已经处理过
                advance = true; // already processed
            else {
                // 锁定表中对应桶,然后进行数据转移,元素要不在在原来i对应的低位桶,要不就在高位(i+n)对应桶
                synchronized (f) {
                    if (tabAt(tab, i) == f) { // 二次确认,防止别其他线程抢
                        Node<K,V> ln, hn;
                        if (fh >= 0) { // hash值大于0,普通的链表
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            // 找到最后一个转以后不在当前索引对应桶中,而是在新扩对应(i+n)索引对应桶中
                            // 因为元素散列算法就是 (hash^(hash>>>16)&0x7FFFFFFF) & (2^x -1),扩容一次
                            // x的值就增加1,表大小容量是2倍增(n=2^x),所以当hash&n=hash&2^x的值不同时,
                            // 则说明该元素在新的表中会散列到高位索引(i+n)中
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            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);
                            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;
                            // 将二叉树的节点分成低位元素和高位元素链表
                            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);
                                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;
                                }
                            }
                            // 如果元素链表数量小于6,则退化成链表,否则就生产二叉树
                            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;
                        }
                    }
                }
            }
        }
    }

5.7 协助扩容

假如对于MOVE节点(forwarding nodes),也是说当前有其他的线程正在扩容,整体尚未完成,但是当前的节点已经完成了转移,那此时就加入到协助扩容环节。

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  Node<K,V>[] nextTab; int sc;
  if (tab != null && (f instanceof ForwardingNode) &&
      (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
      int rs = resizeStamp(tab.length);
      while (nextTab == nextTable && table == tab &&
             (sc = sizeCtl) < 0) {
          if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
              sc == rs + MAX_RESIZERS || transferIndex <= 0)
              break;
          // 多个线程同时进入, sizeCtrl + 1
          if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
              transfer(tab, nextTab);
              break;
          }
      }
      return nextTab;
  }
  return table;
}

六、数据获取

6.1 数据查找

查找元素更为简单,找到列表中对应的桶,根据不同桶的类型,调用的不同的方法找对应元素。

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 计算对应的hash值
        int h = spread(key.hashCode());
        // 找到对应的数组对应的桶, 数组n的必须2的次方(n = 2^i, 其中i<=30)
        // (n - 1) & h) 表示将hash值投影在0 ~ n-1之间的索引值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                // 假如hash值相等, key也相等, 那么表示找到
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                // 如果hash值小于0, 说明是特殊节点ForwardingNode, ReservationNode
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                // eh>=0,为普通节点, 继续搜索链表后面的节点列表
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

6.2 大小获取

size 方法的作用是为返回哈希表中实际存在的键值对的总数。

public int size() {
    // 获取元素数量
    long n = sumCount();
    return ((n < 0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n);
}

final long sumCount() {
    // 统计counterCells和baseCount的数据和
    CounterCell[] as = counterCells; CounterCell a;
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值