04 ConcurrentHashMap1.8源码深入剖析

1 ConcurrentHashMap 的构造方法

/**
* 默认的构造方法为空,不做任何操作,数组长度默认是16
*/
public ConcurrentHashMap() {
}
/**
* 传递初始化容量的构造方法,传递进来一个初始容量,
* ConcurrentHashMap会基于这个值计算一个比这个值大的2的幂次方数作为初始容量
* 与其他版本不同,例如:传递 16 作为参数,它会计算得到 32 作为初始化容量,而不是 16
*/
public ConcurrentHashMap(int initialCapacity) {
  if (initialCapacity < 0)
    throw new IllegalArgumentException();
  int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
             MAXIMUM_CAPACITY :
             tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
  this.sizeCtl = cap;
}

2 添加元素

2.1 sizeCtl 含义解析

这个变量是一个非常重要的变量,而且具有非常丰富的含义,它的值不同,对应的含义也不一样

1.sizeCtl为0,代表数组未初始化, 且数组的初始容量为16
2.sizeCtl为正数,如果数组未初始化,那么其记录的是数组的初始容量,如果数组已经初始化,那么其记录的是数组的扩容阈值
3.sizeCtl为-1,表示数组正在进行初始化
4.sizeCtl小于0,并且不是-1,表示数组正在扩容, -(1+n),表示此时有n个线程正在共同完成数组的扩容操作

2.2 put() 方法

/**
* put() 方法会默认调用 putVal() 方法做具体添加元素逻辑
*/
public V put(K key, V value) {
  return putVal(key, value, false);
}

2.3 putval() 方法

/**
* putVal() 方法做具体添加元素逻辑。根据不同的条件进行不同的插入方案选择。
* 大致分为 直接cas插入,链表插入,树插入以及协助扩容等操作。
* 添加完毕后需要对数组元素个数更新,并且判别是否需要扩容。
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
  // 不接受 null 值,直接抛出空指针异常
  if (key == null || value == null) throw new NullPointerException();
  // 计算 key 的 hash 值
  int hash = spread(key.hashCode());
  // 记录某个桶上元素的个数,如果超过8个,会转成红黑树
  int binCount = 0;
  for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; int n, i, fh;
    // 1.如果数组为空,表示未进行初始化,需要首先进行初始化操作
    if (tab == null || (n = tab.length) == 0)
      tab = initTable();
    // 2.如果 hash 计算得到的桶位置没有元素,利用cas将元素直接添加
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
      if (casTabAt(tab, i, null,
                   new Node<K,V>(hash, key, value, null)))
        break;                   // no lock when adding to empty bin
    }
    // 3.如果hash计算得到的桶位置元素的hash值为MOVED,证明其他线程正在扩容,那么需要协助扩容
    else if ((fh = f.hash) == MOVED)
      tab = helpTransfer(tab, f);
    // 4.hash 计算的桶位置元素不为空,且当前没有处于扩容操作,进行元素添加
    else {
      V oldVal = null;
      // 对当前桶进行加锁,保证线程安全,执行元素添加操作
      // 普通链表 : 尾插法
      // 树化后 : 插入树节点
      // 加锁 为节点头元素添加重量级锁
      synchronized (f) {
        // dcl double check 如果其他线程已经修改头节点,则可以感知
        if (tabAt(tab, i) == f) { 
          // 普通链表节点
          if (fh >= 0) {
            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;
                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;
            // 找到相同元素,赋值
            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                  value)) != null) {
              oldVal = p.val;
              if (!onlyIfAbsent)
                p.val = value;
            }
          }
        }
      }
      if (binCount != 0) {
        // 链表长度大于/等于8,将链表转成红黑树(这个8设计到泊松分布)
        if (binCount >= TREEIFY_THRESHOLD)
          // 树化,因为树化还有满足数组长度是否超过 64
          treeifyBin(tab, i);
        // 如果是重复键,直接将旧值返回
        if (oldVal != null)
          return oldVal;
        break;
      }
    }
  }
  // 添加的是新元素,维护集合长度,并判断是否要进行扩容操作
  addCount(1L, binCount);
  return null;
}

2.4 initTable() 方法

/**
* initTable()方法采用 CAS+自旋的方式线程安全的初始化数组
*/
private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  // cas+自旋,保证线程安全,对数组进行初始化操作
  while ((tab = table) == null || tab.length == 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 {
        // double check
        if ((tab = table) == null || tab.length == 0) {
          // sizeCtl为0,取默认长度16,否则取用户指定的sizeCtl值
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          // 基于初始长度,构建数组对象
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          table = tab = nt;
          // 计算扩容阈值,并赋值给sc,0.75
          sc = n - (n >>> 2);
        }
      } finally {
        // 将扩容阈值,赋值给sizeCtl
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

3 链表树化

3.1 treeifyBin() 方法

/**
* treeifyBin()方法。首先对数组长度判别,查看是否直接扩容数组即可。
* 之后若满足树化条件,则进行树化的步骤,进行双向链表构建,并进行 TreeBin 对象的构建。
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
  Node<K,V> b; int n, sc;
  if (tab != null) {
    // 判别当前数组长度是否超过 64(树化阈值),如果不超过阈值则进行数组的扩容
    // static final int MIN_TREEIFY_CAPACITY = 64;
    if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
      tryPresize(n << 1);
    // 再次核实当前桶存在元素,并且是链表
    else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
      // 并发安全的进行树化
      synchronized (b) {
        // double check
        if (tabAt(tab, index) == b) {
          // 首先进行双向链表的构造
          TreeNode<K,V> hd = null, tl = null;
          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 对象插入到桶中。具体的树化逻辑与HashMap不同,将TreeNode封装到 TreeBin 对象中,方便平衡树的过程中保证桶中的对象不发生变化。便于对first元素进行加锁操作
          setTabAt(tab, index, new TreeBin<K,V>(hd));
        }
      }
    }
  }
}

3.2 TreeBin 初始化

/**
* TreeBin 的构造方法为具体构建红黑树的逻辑。
*/
TreeBin(TreeNode<K,V> b) {
  super(TREEBIN, null, null, null);
  this.first = b;
  TreeNode<K,V> r = null;
  // 遍历双向链表
  for (TreeNode<K,V> x = b, next; x != null; x = next) {
    next = (TreeNode<K,V>)x.next;
    x.left = x.right = null;
    // 构建红黑树的root节点
    if (r == null) {
      x.parent = null;
      x.red = false;
      r = x;
    }
    // 构建红黑树具体逻辑
    else {
      // 获取元素的 key 和 hash
      K k = x.key;
      int h = x.hash;
      Class<?> kc = null;
      // 遍历红黑树
      for (TreeNode<K,V> p = r;;) {
        int dir, ph;
        K pk = p.key;
        // 向左遍历
        if ((ph = p.hash) > h)
          dir = -1;
        // 向右遍历
        else if (ph < h)
          dir = 1;
        // 根据是否实现 comparable 接口的逻辑判别左右遍历
        else if ((kc == null &&
                  (kc = comparableClassFor(k)) == null) ||
                 (dir = compareComparables(kc, k, pk)) == 0)
          dir = tieBreakOrder(k, pk);
        // 记录父节点
        TreeNode<K,V> xp = p;
        // 构建新节点
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
          x.parent = xp;
          if (dir <= 0)
            xp.left = x;
          else
            xp.right = x;
          // 平衡红黑树
          r = balanceInsertion(r, x);
          break;
        }
      }
    }
  }
  this.root = r;
  assert checkInvariants(root);
}

4 计数操作

jdk1.8中使用了一个仿造LongAdder实现的计数器,让计数操作额外使用别的基于分段并发思想的实现的类。

4.1 addCount() 方法

/**
* addCount() 方法主要包括两个功能:
* 1.记录ConcurrentHashMap元素数量,会调用fullAddCount具体执行
* 2.扩容ConcurrentHashMap ,会调用transer方法具体执行扩容
*/
private final void addCount(long x, int check) {
  CounterCell[] as; long b, s;
  // 当CounterCell数组不为空,则优先利用数组中的CounterCell记录数量
  // 或者当baseCount的累加操作失败,会利用数组中的CounterCell记录数量
  if ((as = counterCells) != null ||
      !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
    CounterCell a; long v; int m;
    // 标识是否有多线程竞争
    boolean uncontended = true;
    // 当as数组为空
    // 或者当as长度小于0
    // 或者当前线程对应的as数组桶位的元素为空
    // 或者当前线程对应的as数组桶位不为空,但是累加失败
    if (as == null || (m = as.length - 1) < 0 ||
        (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
        !(uncontended =
          U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
      // 以上任何一种情况成立,都会进入该方法,传入的uncontended是false
      fullAddCount(x, uncontended);
      return;
    }
    if (check <= 1)
      return;
    // 计算元素个数
    s = sumCount();
  }
  // 接着判断是否需要扩容
  if (check >= 0) {
    Node<K,V>[] tab, nt; int n, sc;
    // 当元素个数达到扩容阈值
    // 并且数组不为空
    // 并且数组长度小于限定的最大值
    // 满足以上所有条件,执行扩容
    while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
           (n = tab.length) < MAXIMUM_CAPACITY) {
      // 这个是一个很大的正数
      int rs = resizeStamp(n);
      // sc小于0,说明有线程正在扩容,那么会协助扩容
      if (sc < 0) {
        // 扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移,结束操作
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
          break;
        // 扩容线程加1,成功后,进行协助扩容操作
        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
          // 协助扩容,newTable不为null
          transfer(tab, nt);
      }
      // 没有其他线程在进行扩容,达到扩容阈值后,给sizeCtl赋了一个很大的负数
      else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                   (rs << RESIZE_STAMP_SHIFT) + 2))
        // 扩容,newTable为null
        transfer(tab, null);
      s = sumCount();
    }
  }
}

4.2 fullAddCount() 方法

/**
* fullAddCount() 方法用于将需要添加的个数累加到baseCount上,
* 或者累加到其他CountCell数组中的每个对象中的value属性上
*/
private final void fullAddCount(long x, boolean wasUncontended) {
  int h;
  // 获取当前线程的hash值,如果为 0,则重新进行 hash 值计算
  if ((h = ThreadLocalRandom.getProbe()) == 0) {
    ThreadLocalRandom.localInit();      // force initialization
    h = ThreadLocalRandom.getProbe();
    wasUncontended = true;
  }
  boolean collide = false;                // True if last slot nonempty
  for (;;) {
    CounterCell[] as; CounterCell a; int n; long v;
    // 数组不为空,优先对数组中CouterCell的value累加
    if ((as = counterCells) != null && (n = as.length) > 0) {
      // 线程对应的桶为 null
      if ((a = as[(n - 1) & h]) == null) {
        if (cellsBusy == 0) {            // Try to attach new Cell
          // 创建CounterCell对象
          CounterCell r = new CounterCell(x); // Optimistic create
          // 利用CAS修改cellBusy状态为1,成功则将创建的CounterCell对象放入数组中
          if (cellsBusy == 0 &&
              U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            boolean created = false;
            try {               // Recheck under lock
              CounterCell[] rs; int m, j;
              // double check
              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
            // 桶已经被别的线程放置了 CounterCell 对象,继续循环
            continue;           // Slot is now non-empty
          }
        }
        collide = false;
      }
      // 桶不为空,重新计算线程hash值,然后继续循环
      else if (!wasUncontended)       // CAS already known to fail
        wasUncontended = true;      // Continue after rehash
      // 重新计算了hash值后,对应的桶位依然不为空,对value累加
      // 成功则结束循环
      // 失败则继续下面判断
      else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
        break;
      // 数组被别的线程改变了,或者数组长度超过了可用cpu大小,重新计算线程hash值,否则继续下一个判断
      else if (counterCells != as || n >= NCPU)
        collide = false;            // At max size or stale
      // 当没有冲突,修改为有冲突,并重新计算线程hash,继续循环
      else if (!collide)
        collide = true;
      // 如果CounterCell的数组长度没有超过cpu核数,对数组进行两倍扩容
      else if (cellsBusy == 0 &&
               U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
        try {
          // double check
          if (counterCells == as) {// Expand table unless stale
            // 对 CounterCell 数组进行2倍扩容
            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
      }
      // 重新计算 hash 值
      h = ThreadLocalRandom.advanceProbe(h);
    }
    // CounterCell数组为空,并且没有线程在创建数组,修改标记,并创建数组
    else if (cellsBusy == 0 && counterCells == as &&
             U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
      boolean init = false;
      try {                           // Initialize table
        // double check
        if (counterCells == as) {
          // 初始化容量为2的数组
          CounterCell[] rs = new CounterCell[2];
          // 放入 count 计数
          rs[h & 1] = new CounterCell(x);
          counterCells = rs;
          init = true;
        }
      } finally {
        cellsBusy = 0;
      }
      if (init)
        break;
    }
    // 数组为空,并且有别的线程在创建数组,那么尝试对baseCount做累加,成功就退出循环,失败就继续循环
    else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
      break;                          // Fall back on using base
  }
}

4.3 sumCount() 方法

/**
* sumCount() 方法会聚合所有累加单元和baseCount的值
*/
final long sumCount() {
  CounterCell[] as = counterCells; CounterCell a;
  // 获取baseCount的值
  long sum = baseCount;
  if (as != null) {
    // 遍历CounterCell数组,累加每一个CounterCell的value值
    for (int i = 0; i < as.length; ++i) {
      if ((a = as[i]) != null)
        sum += a.value;
    }
  }
  return sum;
}

5 扩容操作

多线程协助扩容的操作会在两个地方被触发:

① 当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素。

② 当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizeCtl的值小于0,并且新数组不为空,这个时候,会去协助扩容。

5.1 transfer() 方法

/**
* ConcurrentHashMap触发扩容的时机与HashMap类似,
* 要么是在将链表转换成红黑树时判断table数组的长度是否小于阈值(64),
* 如果小于就进行扩容而不是树化,要么就是在添加元素的时候,
* 判断当前Entry数量是否超过阈值,如果超过就进行扩容。
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
  int n = tab.length, stride;
  // 如果是多cpu,那么每个线程划分任务,最小任务量是16个桶位的迁移
  if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
    stride = MIN_TRANSFER_STRIDE; // subdivide range
  // 如果是扩容线程,此时新数组为null
  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;
  // 已迁移的桶,会用这个对象节点占位(这个节点的hash值为-1--MOVED)
  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;
    while (advance) {
      int nextIndex, nextBound;
      // i记录当前正在迁移桶位的索引值
      // bound记录下一次任务迁移的开始桶位
      // --i >= bound 成立表示当前线程分配的迁移任务还没有完成
      if (--i >= bound || finishing)
        advance = false;
      // 没有元素需要迁移 后续会去将扩容线程数减1,并判断扩容是否完成
      else if ((nextIndex = transferIndex) <= 0) {
        i = -1;
        advance = false;
      }
      // 计算下一次任务迁移的开始桶位置,并将这个值赋值给transferIndex
      else if (U.compareAndSwapInt
               (this, TRANSFERINDEX, nextIndex,
                nextBound = (nextIndex > stride ?
                             nextIndex - stride : 0))) {
        bound = nextBound;
        i = nextIndex - 1;
        advance = false;
      }
    } 
    // 如果没有更多的需要迁移的桶位,就进入该if
    if (i < 0 || i >= n || i + n >= nextn) {
      int sc;
      // 扩容结束后,保存新数组,并重新计算扩容阈值,赋值给sizeCtl
      if (finishing) {
        nextTable = null;
        table = nextTab;
        sizeCtl = (n << 1) - (n >>> 1);
        return;
      }
      // 扩容任务线程数减1
      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
      }
    }
    // 当前迁移的桶位没有元素,直接在该位置添加一个fwd节点
    else if ((f = tabAt(tab, i)) == null)
      advance = casTabAt(tab, i, null, fwd);
    // 当前节点已经被迁移
    else if ((fh = f.hash) == MOVED)
      advance = true; // already processed
    // 当前节点需要迁移,加锁迁移,保证多线程安全
    // 此处迁移逻辑和jdk7的ConcurrentHashMap相同,不再赘述
    else {
      synchronized (f) {
        if (tabAt(tab, i) == f) {
          Node<K,V> ln, hn;
          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;
              }
            }
            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;
              }
            }
            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.2 helpTransfer() 方法

/**
* helpTransfer() 方法是用于其他线程协助扩容的功能。
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  Node<K,V>[] nextTab; int sc;
  // 当数组不为空并且fwd为占位对象并且扩容的新数组存在时,需要进行协助扩容
  if (tab != null && (f instanceof ForwardingNode) &&
      (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
    int rs = resizeStamp(tab.length);
    // CAS+自旋
    while (nextTab == nextTable && table == tab &&
           (sc = sizeCtl) < 0) {
      // 扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移,结束操作
      if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
          sc == rs + MAX_RESIZERS || transferIndex <= 0)
        break;
      // 扩容线程加1,成功后,进行协助扩容操作
      if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
        // 扩容,传递一个不是null的nextTab
        transfer(tab, nextTab);
        break;
      }
    }
    return nextTab;
  }
  return table;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容