【collection】2.java容器之HashMap&LinkedHashMap&Hashtable2
ConcurrentHashMap
put操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 本质上和hashmap没有什么差别,都是把hashcode进行对半异或,这样就可以用一半的位数,集合了32位长度的信息
int hash = spread(key.hashCode());
int binCount = 0;
Node<K,V>[] tab = table;
// 这里循环的目的是cas的重试操作
for (;;) {
// 指向待插入元素应当插入的位置
Node<K,V> f;
// 元素f对应的哈希值
int fh;
// 当前hash表数组的长度容量
int n;
int i;
// 如果哈希数组还未初始化,或者容量无效,则需要初始化一个哈希数组
if (tab == null || (n = tab.length) == 0) {
tab = initTable();
// 这里n -1 & hash 是经典取余操作,参考之前hashmap
} else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 如果当前位置是空的,那么就可以通过cas设置对应的值
Node<K, V> newNode = new Node<K,V>(hash, key, value, null);
// 用一次 CAS 操作将这个新值放入其中即可,这个 put 操作差不多就结束了,可以拉到最后面了
// 如果 CAS 失败,那就是有并发操作,进到下一个循环就好了
if (casTabAt(tab, i, null, newNode)) {
// 插入完成,跳出循环,如果更新失败,重新循环进入
break; // no lock when adding to empty bin
}
} else if ((fh = f.hash) == MOVED) {
/*
* 如果待插入元素所在的哈希槽上已经有别的结点存在,且该结点类型为MOVED
* 说明当前哈希数组正在扩容中,此时,可以尝试加速扩容过程
*/
tab = helpTransfer(tab, f);
} else {
V oldVal = null;
// 这里避免并发,对f节点的引用进行上锁
synchronized (f) {
// 如果tab[i]==f,则代表当前待插入状态仍然可信
if (tabAt(tab, i) == f) {
// fh > 0 标识不是在扩容,是正常节点
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) {
// 哈希槽(链)上的元素数量增加到TREEIFY_THRESHOLD后,这些元素进入波动期,即将从链表转换为红黑树
if (binCount >= TREEIFY_THRESHOLD) {
// 注意,这里也只是锁了这一个节点,注意,这里不一定一定是转换为红黑树
// 如果整个tab长度是小于64的话,这里会选择自动扩容,如果已经超过64了,才考虑转换红黑树
treeifyBin(tab, i);
}
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
get和remove操作
略,就是根据索引找节点
扩容
核心方法就是transfer
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length;
// stride 在单核下直接等于 n,多核模式下为 (n>>>3)/NCPU,最小值是 16
// 将这 n 个任务分为多个任务包,每个任务包有 stride 个任务
int stride = (NCPU > 1) ? (n >>> 3) / NCPU : n;
if (stride < MIN_TRANSFER_STRIDE) {
stride = MIN_TRANSFER_STRIDE; // subdivide range
}
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 翻译过来就是正在被迁移的 Node
// 这个构造方法会生成一个Node,key、value 和 next 都为 null,关键是 hash 为 MOVED
// 后面我们会看到,原数组中位置 i 处的节点完成迁移工作后,
// 就会将位置 i 处设置为这个 ForwardingNode,用来告诉其他线程该位置已经处理过了
// 所以它其实相当于是一个标志。, 这个在put的时候会判断节点hash值,用来判断是否需要协助扩容
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// advance 指的是做完了一个位置的迁移工作,可以准备做下一个位置的了
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;
if (--i >= bound || finishing) {
advance = false;
} else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
} 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) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
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 {
// 真正的开始数据迁移,先对f节点上锁,f是tab中的一个位置
synchronized (f) {
// 保证数据正确性没有发生变化
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
// 头节点的 hash 大于 0,说明是链表的 Node 节点
if (fh >= 0) {
// 下面这一块和 Java7 中的 ConcurrentHashMap 迁移是差不多的,
// 需要将链表一分为二,
// 找到原链表中的 lastRun,然后 lastRun 及其之后的节点是一起进行迁移的
// lastRun 之前的节点需要进行克隆,然后分到两个链表中
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;
// ph是这个节点的hash值&n如果为0,说明再n-1部分,位置没变,还是放入之前的位置
if ((ph & n) == 0) {
ln = new Node<K,V>(ph, pk, pv, ln);
} else {
// 不为0,说明再n-1上面的位置,位置变了,那么就重新设置位置
hn = new Node<K,V>(ph, pk, pv, hn);
}
}
// 其中的一个链表放在新数组的位置 i
setTabAt(nextTab, i, ln);
// 把head放到数组i+n的位置
setTabAt(nextTab, i + n, hn);
// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,
// 其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了
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;
}
}
}
}
}
}