作者看源码的过程一般都是从其构造方法开始,然后Put和Get方法,主要是理解其底层实现原理,其他方法节本相同,看源码的过程中会对源码中的重要部分进行详细的注释说明
//空构造方法什么也没有做
public ConcurrentHashMap() {
}
//参数为 初始化容量的大小
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;
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
//其实调用了三个参数的构造方法
this(initialCapacity, loadFactor, 1);
}
//三个参数的构造方法
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;
}
public V put(K key, V value) {
//调用了putVal方法,下面跟进去看
return putVal(key, value, false);
}
/**
*第几次调用该方法,所有的注释前面都加 ‘几’
*
*
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
//1 这里不允许key和value为空值
if (key == null || value == null) throw new NullPointerException();
//1 根据hashCode计算得到hash值
int hash = spread(key.hashCode());
int binCount = 0;
//1 一个for循环 注释前面的第二个数字代表循环次数
//1 table 是一个属性变量 Node[]类型
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//1 1 当table没有初始化时,即为null时
if (tab == null || (n = tab.length) == 0)
//1 1 调用这个方法,我们跟进去,下面看initTable方法,第一次主要调用就是这个方法,初始化
//1 1 table
tab = initTable();
//1 2 初始化结束后,循环继续 根据hash找到table对应下标的元素是否为null,如果为null
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//1 借助sun.misc.Unsafe采用CAS乐观锁的思想来把值存入table对应下标的链表中
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//3 1 当插入节点的hash值为-1,就会进行扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//2 1 其他情况走这个else
else {
V oldVal = null;
//2 1 这里使用了同步代码块,因此是线程安全的,这里用的是头结点作为锁,只是锁住一个链表
//2 1 其他链表仍然可以并发插入
synchronized (f) {
//2 1 tabAt底层仍然使用Unsafe类
//2 1 Unsafe的方法是具有原子性
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
//2 1 遍历链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
//2 1 判断key和链表中的元素的key是否完全相同
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
//2 1 相同则覆盖原来的value值
if (!onlyIfAbsent)
e.val = value;
//2 1 跳出内层循环
break;
}
//2 1 不相同
Node<K,V> pred = e;
//2 1 判断是否是链表最后一个元素
if ((e = e.next) == null) {
//2 1 这把新节点链在链表末尾
pred.next = new Node<K,V>(hash, key,
value, null);
//2 1 跳出内层循环
break;
}
}
}
//2 1 是否是树节点类型
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//2 1 插入红黑树中
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果table下标的链表或者红黑树的个数大于等于8
if (binCount >= TREEIFY_THRESHOLD)
//将链表转换为红黑树
treeifyBin(tab, i);
if (oldVal != null)
//返回旧的值
return oldVal;
//跳出外循环
break;
}
}
}
//总容量个数加1
addCount(1L, binCount);
return null;
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
/*sizeCtl 使用与table初始化和大小调整控制的变量
//当为负数时,表示table正在初始化或者调整大小
//-1表示正在初始化
//其他值表示 -(1+正在调整大小的活动线程数)
//当为正数时,表示需要调整的最大容量值,也就是当元素数量到达这个数时,需要进行调整扩容
*/
while ((tab = table) == null || tab.length == 0) {
//小于0时,说明其他线程正在初始化或者调整大小
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 不小于0,将SIZECTL赋值为-1,表示正在初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//=16
//创建一个长度为16的数组
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//table 属性赋值
table = tab = nt;
sc = n - (n >>> 2);//=12
}
} finally {
sizeCtl = sc;//等于需要调整的最大值
}
//跳出循环
break;
}
}
return tab;
}