ConcurrentHashMap源码
前言
HashMap存在线程安全的问题,HashTable是线程安全的,但是其是对整个hash表加锁,会有很大的性能问题,ConcurrentHashMap可以高效的支持并发操作
- JDK1.7 采用Segment(段锁)的概念实现
- JDK1.8 摒弃了Segment,而采用了Synchronized + CAS算法实现,沿用JDK1.8中HashMap的思想,底层采用 数组 + 链表 + 红黑树 的结构
一、源码结构
1.1、重要成员属性
// table最大容量,为2的幂次方 private static final int MAXIMUM_CAPACITY = 1 << 30; // 默认table初始容量大小 private static final int DEFAULT_CAPACITY = 16; // 默认支持并发更新的线程数量 private static final int DEFAULT_CONCURRENCY_LEVEL = 16; // table的负载因子 private static final float LOAD_FACTOR = 0.75f; // 链表转换为红黑树的节点数阈值,超过这个值,链表转换为红黑树 static final int TREEIFY_THRESHOLD = 8; // 在扩容期间,由红黑树转换为链表的阈值,小于这个值,resize期间红黑树就会转为链表 static final int UNTREEIFY_THRESHOLD = 6; // 转为红黑树时,红黑树中节点的最小个数 static final int MIN_TREEIFY_CAPACITY = 64; // 扩容时,并发转移节点(transfer方法)时,每次转移的最小节点数 private static final int MIN_TRANSFER_STRIDE = 16; // 以下常量定义了特定节点类hash字段的值 static final int MOVED = -1; // ForwardingNode类对象的hash值 static final int TREEBIN = -2; // TreeBin类对象的hash值 static final int RESERVED = -3; // ReservationNode类对象的hash值 static final int HASH_BITS = 0x7fffffff; // 普通Node节点的hash初始值 // table数组 transient volatile Node<K,V>[] table; // 扩容时,下一个容量大小的talbe,用于将原table元素移动到这个table中 private transient volatile Node<K,V>[] nextTable; // 基础计数器 private transient volatile long baseCount; // table初始容量大小以及扩容容量大小的参数,也用于标识table的状态 // 其有几个值来代表也用来代表table的状态: // -1 :标识table正在初始化 // - N : 标识table正在进行扩容,并且有N - 1个线程一起在进行扩容 // 正数:初始table的大小,如果值大于初始容量大小,则表示扩容后的table大小。 private transient volatile int sizeCtl; // 扩容时,下一个节点转移的bucket索引下标 private transient volatile int transferIndex; // 一种自旋锁,是专为防止多处理器并发而引入的一种锁,用于创建CounterCells时使用, // 主要用于size方法计数时,有并发线程插入而计算修改的节点数量, // 这个数量会与baseCount计数器汇总后得出size的结果。 private transient volatile int cellsBusy; // 主要用于size方法计数时,有并发线程插入而计算修改的节点数量, // 这个数量会与baseCount计数器汇总后得出size的结果。 private transient volatile CounterCell[] counterCells;
1.2、内部类

- Node是ConcurrentHashMap最核心的内部类,map中的数据都存储其中,与HashMap的Node内部类很类似,区别于ConcurrentHashMap在V val 和next属性前加Volatile , setValue()方法不允许外部调用,提供find() 辅助get()
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; Node(int hash, K key, V val, Node<K,V> next) { this.hash = hash; this.key = key; this.val = val; this.next = next; } public final K getKey() { return key; } public final V getValue() { return val; } public final int hashCode() { return key.hashCode() ^ val.hashCode(); } public final String toString(){ return key + "=" + val; } public final V setValue(V value) { throw new UnsupportedOperationException(); } public final boolean equals(Object o) { Object k, v, u; Map.Entry<?,?> e; return ((o instanceof Map.Entry) && (k = (e = (Map.Entry<?,?>)o).getKey()) != null && (v = e.getValue()) != null && (k == key || k.equals(key)) && (v == (u = val) || v.equals(u))); } /** * Virtualized support for map.get(); overridden in subclasses. */ Node<K,V> find(int h, Object k) { Node<K,V> e = this; if (k != null) { do { K ek; if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; } while ((e = e.next) != null); } return null; } }
- TreeNode:树节点,当链表长度达到一定值时,会转换成TreeNode。与HashMap不同的是,ConcurrentHashMap不直接转换红红黑树,而是将这些节点包装成TreeNode方在TreeBin对象中,由TreeBin完成对红黑树的包装。
- TreeBin 不负责包装 key/value 信息,而是包装很多TreeNode节点。TreeBin代替了TreeNode的根节点,即实际存储在ConcurrentHashMap “数据”中存放的是TreeBin对象,而不是TreeNode对象,另外该类还带有读写锁。
static final class TreeBin<K,V> extends Node<K,V> { TreeNode<K,V> root; volatile TreeNode<K,V> first; volatile Thread waiter; volatile int lockState; // 构造函数 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; if (r == null) { x.parent = null; x.red = false; r = x; } else { // 红黑树结构 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; 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); } }
- ForwardingNode:在调用transfer()方法期间,插入bucket头部的节点,主要用来标识在扩容时元素的移动状态,即是否在扩容时还有并发的插入节点,并保证该节点也能够移动到扩容后的表中。
- ReservationNode:占位节点,不存储任何信息,无实际用处,仅用于computeIfAbsent和compute方法中。
二、核心方法源码
2.1、put() 方法
基本流程如下:
- 自旋检测table是否初始化,保证首次插入在table初始化完成之后(懒加载)
- 自旋检测插入元素在table的索引下标是否正在扩容(ForwardingNode ),如果是则加入扩容,扩容完成后再插入
- 插入的索引下标为空直接赋值,不为空则对链表或者TreeBin的head加Synchronized锁,再插入更新。
- 插入之后校验链表元素个数,如果达到树转化阈值,则将链表转换为红黑树
- 将索引所在的链表或者红黑树的最新节点数量更新到CounterCell中
public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); // 计算hash值 int hash = spread(key.hashCode()); int binCount = 0; // table中索引下标代表的链表或红黑树中的节点数 for (Node<K,V>[] tab = table;;) { // 自旋等待table初始化,或等待扩容或等待锁 然后再插入 // f:key对应位置的索引节点,可是链表head,也可能红黑树head // n:table的长度;i:key在table的下标索引; fh:head节点的hash值 Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) // table为空 执行初始化 tab = initTable(); // 定位到索引下标节点(head)为null,第一次在此索引处插入数据。 casTabAt 采用Unsafe的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 } // MOVED=-1 代表ForwardingNode 节点,标识table正在扩容,帮助扩容完成后再插入元素 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { // 索引坐标存在值,且为普通元素,给head加锁后执行插入或者更新 V oldVal = null; synchronized (f) { 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)时,转为红黑树结构 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } // 如果插入新元素,则将链表或者红黑树最新元素个数赋值到CounterCell中 addCount(1L, binCount); return null; }
putVal()方法中调用的方法
- spread(int hash): 计算key的hash值,高16位也参加运算,避免因为table.size 较小而出现频繁的hash冲突
static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }
- initTable()
private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) // sizeCtl<0 : table初始化,让出CPU资源等待初始化完成 Thread.yield(); // lost initialization race; just spin 放弃初始化竞争,仅自旋 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 将sizeCtl赋值为 -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; sc = n - (n >>> 2); } } finally { // 初始化的时候,为什么sizeCtl的值要乘以 3/4? 类似扩容阈值,不确定 sizeCtl = sc; } break; } } return tab; }
- tabAt() & casTabAt()
这两个方法都是使用Unsafe的CAS方法确保读取和操作的原子性,保证线程安全
注:虽然Node[] table已经使用了volatile修饰,但是只对数组本身最有效,数组内的元素没有影响,所以需要使用Unsafe的CAS方法操作
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); } static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) { return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); }
- helpTransfer() 辅助扩容方法,核心方法是transfer(),扩容前更新sizeCtl的值,表明并发扩容的参数
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { Node<K,V>[] nextTab; int sc; // 数据校验:table不为空,节点是ForwardingNode,f.nextTable不为空 if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { // 扩容标识符,只是作为是否扩容成功的校验,没有实际意义 // Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)) (n 32位二进制1前面0的个数) | 2^15 // 当扩容完成,n前的0个数-1,标识符发生变化,扩容结束 int rs = resizeStamp(tab.length); while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { // sizeCtl >>> 16 != rs 标识符,标识符发生变化,扩容结束:在第一次扩容开始的时候,sizeCtl = (标识符 << 16)+2 // sc == rs +1, 扩容结束:默认第一个扩容线程 sc = rs + 2,当扩容结束 sc = sc-1 // sc == rs + (2^16 - 1) 达到最大帮助线程数量 // 扩容下标调整 (扩容结束) if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { // sc+=1,表示添加线程帮助扩容 transfer(tab, nextTab); // 执行扩容 break; } } return nextTab; } return table; }
扩容时,sizeCtl的值,高16位存储标识符,低16位存储扩容线程数
![]()

- addCount():将新的链表or红黑树元素数赋值到CounterCell中, 根据元素个数决定是否需要扩容
baseCount & CounterCell 并发计算元素数量的方式值得借鉴
/** * 添加到count,如果table太小并且没有在扩容,则初始化执行扩容。如果已经正在扩容,可以的话帮助扩容。 * 扩容结束后再次校验table元素容量,是否需要再次扩容,因为扩容是滞后性的 * * @param x the count to add * @param check if <0, don't check resize, if <= 1 only check if uncontended */ private final void addCount(long x, int check) { CounterCell[] as; long b, s; // 如果首次执行addCount,并且尝试CAS对baseCount计数失败,表示有竞争 或者 首次执行addCount 执行下面操作 // 如果 CAS操作baseCount加成功,则不对CounterCell赋值;如果失败(多线程有竞争),则将新增数量添加到Countercell if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { fullAddCount(x, uncontended); return; } if (check <= 1) return; s = sumCount(); } if (check >= 0) { // 重新校验table Node<K,V>[] tab, nt; int n, sc; // 1、如果表格容量不足,并且table不为空,小于最大容量,执行扩容 // 2、当一个线程完成数据迁移之后,如果集合还再扩容,则会继续循环,继续加入扩容队伍,申请后面的迁移任务 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { // 获取标识符 int rs = resizeStamp(n); if (sc < 0) { // 正在扩容 // 对比helpTransfer(),扩容结束 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } // 扩容的第一个线程,sizeCtl = (标识符 << 16) + 2 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); s = sumCount(); } } } // 计算总table中元素数量:baseCount + CountterCell,用户size() 精确计算table元素数量 final long sumCount() { 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; } public int size() { long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); }
2.2、get() 方法
- 计算key对应的hash值,定位table索引
- 如果hash值 < 0,根据节点类型确定查询方式,ForwardingNode 则会到扩容之后的 nextTable,进行数据查询
- 若table索引下元素(head节点)为普通链表,按照链表的形式遍历
- 若table索引下元素为红黑树TreeBin节点,则按红黑树的方式查找 find
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; } // hash小于0,说明正在扩容或者为红黑树,分别对应ForwardingNode & TreeBin 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; }
ForwardingNode的查询方式
Node<K,V> find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node<K,V>[] tab = nextTable;;) { Node<K,V> e; int n; if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; for (;;) { int eh; K ek; if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; if (eh < 0) { if (e instanceof ForwardingNode) { // 如果节点又在扩容,再到新的扩容节点查询结果 tab = ((ForwardingNode<K,V>)e).nextTable; continue outer; } else return e.find(h, k); } if ((e = e.next) == null) return null; } } }
TreeBin的查询方式
// TreeBin的属性 static final int WRITER = 1; // set while holding write lock static final int WAITER = 2; // set when waiting for write lock static final int READER = 4; // increment value for setting read lock /** * Returns matching node or null if none. Tries to search * using tree comparisons from root, but continues linear * search when lock not available. */ final Node<K,V> find(int h, Object k) { if (k != null) { for (Node<K,V> e = first; e != null; ) { int s; K ek; // 元素节点有写锁,按照链表的方式遍历查询 if (((s = lockState) & (WAITER|WRITER)) != 0) { if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; e = e.next; } else if (U.compareAndSwapInt(this, LOCKSTATE, s,s + READER)) { // 加读锁 TreeNode<K,V> r, p; try { // 只有读锁的情况,按照红黑树的方式查询 p = ((r = root) == null ? null : r.findTreeNode(h, k, null)); } finally { Thread w; if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER|WAITER) && (w = waiter) != null) LockSupport.unpark(w); } return p; } } } return null; }
解析:在红黑树遍历查询的时候,如果有读锁,为什么还是按照链表的方式遍历??
static final class TreeBin<K,V> extends Node<K,V>
static final class TreeNode<K,V> extends Node<K,V>
Node 类中有next 属性,TreeBin & TreeNode只是按照添加了一些独有的属性。Node本身可以成链表,TreeBin & TreeNode继承自Node,当然也可以成链表,其实真正存储时,红黑树其实是以链表的方式进行存储的,只不过逻辑上TreeBin & TreeNode支持了红黑树的属性 [ first 、 parent、left、right、prev],元素进行逻辑关联,就形成了一颗树。
红黑树的查找方法:二叉树查找方式
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) { if (k != null) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> q; TreeNode<K,V> pl = p.left, pr = p.right; if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; else if ((pk = p.key) == k || (pk != null && k.equals(pk))) return p; else if (pl == null) p = pr; else if (pr == null) p = pl; else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) p = (dir < 0) ? pl : pr; else if ((q = pr.findTreeNode(h, k, kc)) != null) return q; else p = pl; } while (p != null); } return null; }
2.3、扩容
何时触发扩容:
- putVal()中 addCount() 方法,如果当前元素个数达到扩容阈值,执行扩容
- helpTransfer() : 扩容状态下,其他线程对集合进行插入、修改、删除、合并、compute等操作时遇到ForwardingNode,节点会调用该帮助扩容方法
- tryPresize() : putAll()批量插入或者插入节点后,存在链表长度达到 8 个或以上,但数组长度为 64 以下时会触发扩容
/** * 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) { int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int 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用于记录扩容阈值 sizeCtl = sc; } } } else if (c <= sc || n >= MAXIMUM_CAPACITY) // 未达到扩容阈值,或者n>= max break; else if (tab == table) { // 达到扩容阈值,并且其他线程没有对table进行修改 int rs = resizeStamp(n); // 扩容标识 if (sc < 0) { // 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; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); } } }
扩容源码 transfer()
/** * 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 每个线程处理的桶数量 // 根据CPU的资源计算,计算每个线程处理的桶数量,每个线程处理的桶数量一致 // 每个线程最少处理16个桶,如果计算出的数值小于16,则一个线程处理16个桶 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // 初始化新数据(原数组长度的两倍) 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指向原数组最右边的桶,数组下标最大的位置 transferIndex = n; } int nextn = nextTab.length; // 新建一个占位(传递)对象,该对象Hash值为 -1 ,独享存在时表明集合处在扩容状态,key、value、next均为null,nextTable属性指向扩容后的数组 // 占位对象有两个作用: 【看迁移之后的操作】 // 1、表明该位置的元素已经迁移完成,处于扩容的状态 // 2、如果扩容期间有查询操作,遇到转发节点,会到新的数组中查询,不会阻塞查询操作 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); // 标识是否可以执行一下个桶的迁移,true标识已经处理完当前桶,可以继续处理下一个桶 boolean advance = true; //该标识用于控制扩容何时结束,该标识还有一个用途是最后一个扩容线程会负责重新检查一遍数组查看是否有遗漏的桶 boolean finishing = false; // to ensure sweep before committing nextTab // 该循环是处理一个stride长度的任务, i会被赋值为一个stride内的最大下标,bound会被赋值为一个stride内的最小下标 // 循环减小 i 的值,直到 i 小于 bound 值,结束一个长度为 stride 的迁移任务 // 结束这次的任务后会通过外层 addCount、helpTransfer、tryPresize 方法的 while 循环达到继续领取其他任务的效果 for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; // 每执行完一个桶的迁移任务,i - 1,直到一个 stride长度的桶元素迁移完成 if (--i >= bound || finishing) advance = false; // transferIndex <= 0 说明,数组的 hash 桶已经分配完,i 赋值为 -1,下面的代码会根据i 值退出当前线程的扩容操作 else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } // 首次进入 for循环才会进入该逻辑,设置bound和i 的值,用bound & i 确定任务执行的数组桶区间 else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ?nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } if (i < 0 || i >= n || i + n >= nextn) { // i<0 看上面的注释 标识长度为stride 的迁移任务已经完成 int sc; // 扩容结束,将nextTable 置为null,标识扩容结束,将table执行新数组,sizeCtl设置为新数据长度的扩容阈值 if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } // 每个线程扩容结束之后会更新 sizeCtl的值,进行减 1 操作 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { // (sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT,说明当前线程不是扩容线程中的最后一个线程,直接return if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; // 如果 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT 说明是最后一个扩容线程 // 该判断成立的原因:第一条扩容线程进行了如下操作 // U.compareAndSwapInt(this, SIZECTL, sc, (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) // 设置 i = n,以便重新检查一遍数组,防止有遗漏未成功迁移的桶 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) // 如果该位置 hash值为 MOVED(-1) 说明已经被其他线程迁移完成,将advance 设置为true,以便继续下一个桶的迁移 advance = true; // already processed 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; // 遍历整个链表,找出 lastRun 节点 【lastRun的含义看下面的图解】 // lastRun 简单来说就是:高低标识位元素最后一个交界节点 例:1-1-0-0-1-[0]-0 []代表lastRun 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) { // 如果lastRun hash值为0,代表该节点是低位,赋值给ln ln = lastRun; hn = null; } else { // 如果 lastRun hash值不为0(为1),代表高节点是高位,赋值给hn hn = lastRun; ln = null; } // 从头节点到lastRun 遍历,高低位元素分别使用头插法放入ln & hn 形成新的链表 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); } // ln 低位节点链表插入新数组 i 节点位置 setTabAt(nextTab, i, ln); // hn 高位节点链表插入新数组 i + n 节点位置 setTabAt(nextTab, i + n, hn); // 原数组 i 节点位置插入占位符,用于查询时候转发到新数组,并表示该元素正处于扩容阶段(迁移完成) setTabAt(tab, i, fwd); advance = true; // 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) { // 低位节点,尾插入组成 lo 低位链表 if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { // 高位节点,尾插发组成 hi 高位链表 if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } // 校验lo & hi 链表是否满足红黑树转换条件,满足则转换成红黑树 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; // ln 低位节点链表插入新数组 i 节点位置 setTabAt(nextTab, i, ln); // hn 高位节点链表插入新数组 i + n 节点位置 setTabAt(nextTab, i + n, hn); // 原数组 i 节点位置插入占位符,用于查询时候转发到新数组,并表示该元素正处于扩容阶段(迁移完成) setTabAt(tab, i, fwd); advance = true; } } } } } }
图解
CPU核数与迁移任务hash桶数量分配的关系

单线程下线程的任务分配与迁移操作


多线程如何分配任务?

普通链表如何迁移?这里链表的迁移,考虑到效率问题,使用的是 头插法

什么是 lastRun 节点?

红黑树如何迁移?

hash桶迁移中以及迁移后如何处理存取请求?

多线程迁移任务完成后的操作



浙公网安备 33010602011771号