目录
一、存储结构的演变
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;
}