concurrenthashmap 系列的源码分析 主要是 介绍了putVal方法的源码,这里面也包括了 concurrenthashmap 大部分的常用的方法,例如 initTable、 spread、transfer、treeifyBin、addCount等等
带着问题开始吧!答案会在concurrenthashmap 系列文章的某个角落。。。
question*******1 hashmap扩容时每个entry需要再计算一次hash吗 ?:不需要
question*******2 hashmap的数组长度为什么要保证是2的幂?
一、概述
A hash table supporting full concurrency of retrievals and high expected concurrency for updates
支持检索的完全并发性和更新的高期望并发性的哈希表
二、重要属性
1. sizeCtl
/**
* Table initialization and resizing control. When negative, the
* table is being initialized or resized: -1 for initialization,
* else -(1 + the number of active resizing threads). Otherwise,
* when table is null, holds the initial table size to use upon
* creation, or 0 for default. After initialization, holds the
* next element count value upon which to resize the table.
*/
private transient volatile int sizeCtl;
用来控制初始化和扩容的属性。 -N: 表示正有N-1个线程执行扩容操作
2. transferIndex
/**
* The next table index (plus one) to split while resizing.
* 扩容索引,表示已经分配给扩容线程的table数组索引位置。主要用来协调多个线程,并发安全地
* 获取迁移任务(hash桶)
*/
private transient volatile int transferIndex;
3.用来计算concurrentHashMap长度的属性 (可以参考longAddr)
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
/**
* baseCount用于记录元素的个数,对这个变量的修改操作是基于CAS的.
*/
/**
* Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
*/
private transient volatile int cellsBusy;
/**
* counterCells是一个元素为CounterCell的数组,该数组的大小与当前机器的CPU数量有关,
* 并且它不会被主动初始化,只有在调用fullAddCount()函数时才会进行初始化。
*/
/**
* Table of counter cells. When non-null, size is a power of 2.
*/
private transient volatile CounterCell[] counterCells;
三、putVal方法
/** Implementation for put and putIfAbsent */
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;
//死循环 何时插入成功 何时跳出
(1)数组要插入的位置没有值 (2)链表或树中插入成功
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//如果table为空的话,初始化table
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//根据hash值计算出在table里面的位置 question*******2
长度16或其他2的幂次方,Length - 1的值的二进制所有的位均为1,这种情况下,Index的结果等于hashCode的最后几位。只要输入的hashCode本身符合均匀分布,Hash算法的结果就是均匀的。
一句话,HashMap的长度为2的幂次方的原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀分布。
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
}
//帮助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//锁住桶中的头结点,保证之后插入的并发安全
synchronized (f) {
if (tabAt(tab, i) == f) {
//fh〉0 说明这个节点是一个链表的节点 不是树的节点 spread方法保证了结果非负数
if (fh >= 0) {
binCount = 1;
//在这里遍历链表所有的结点
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果hash值和key值相同 onlyIfAbsent=false 则覆盖对应结点的value值
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 (treeifyBin 中会再次判断数组长度>64) 就需要把链表转换为树结构
/**
*某个桶的元素大小超过了8个,达到了转换为红黑树的阈值,但是数组的长度<64,此
*时只是通过扩容来解决容量问题,而不会转换为红黑树,而过数组长度>=64,则可以
*转换为红黑树;
*/
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//将当前ConcurrentHashMap的元素数量+1
addCount(1L, binCount);
return null;
}
下面附上流程图:
ps:其他方法待续。。。。