ConcurrentHashMap源码分析
构造方法
/**
* Creates a new, empty map with the default initial table size (16).
* 默认大小16
*/
public ConcurrentHashMap() {
}
/**
* 自定义大小的构造函数
*/
public ConcurrentHashMap(int initialCapacity) {
// 初始大小小于0抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
/**
* Returns a power of two table size for the given desired capacity.
* 返回最接近传参值的2的次方数
* 参考 Hackers Delight 第3.2节
*/
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 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;
}
初始化table
初始化table的源码如下,关于为什么代码13行中,为什么要重复判断table是否为空,举例说明:
①当有两个线程A和B同时触发initTable时,此时的sizeCtl要么是初始值,要么是已经初始化成功后的值
②当线程A和B都进入循环时,判断执行U.compareAndSwapInt(this, SIZECTL, sc, -1)【其中U是sun.misc.Unsafe,Unsafe是借助C调用Cpu底层指令的类,SIZECTL是sizeCtl的偏移量】,此时线程A和B只有一个返回true,并且sc置为-1,这一步为原子操作
③假设线程A成功执行,sc置为-1,那么线程B则返回false并继续循环,此时由于线程A还未执行完,sc为-1,B则一直让出CPU
④此时线程A执行判断sc是否大于0,大于0则赋值n为sc,否则置为默认值16,然后创建一个大小为n的Node数组,并赋值到table
⑤sc=n-(n>>>2),sizeCtl置为新的sc值,此时线程A结束初始化,线程B可能触发运行,这时候在代码第13行判断table是否为空,就能防止重新创建table了
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// table为空或者长度为0才初始化
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // sc用于并发的线程判断,小于0让出CPU
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 这里重复判空,是为了防止多线程重复创建table
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
添加元素
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key和value为空的抛出空指针异常
if (key == null || value == null) throw new NullPointerException();
// 重算hashcode,将hashcode的高位也应用到hashcode的取值,增加hashcode的复杂度,降低碰撞
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// table为空的情况或者长度为0,初始化table
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果table中查询不到该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
}
// 如果f.hash值为MOVED ,即等于-1,代表有线程在处理
else if ((fh = f.hash) == MOVED)
// 帮助扩容,后面分析这个方法
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 对首个节点进行加锁,减少线程冲突
synchronized (f) {
// 这里再次获取hash对应的table节点,判断节点是否一致,防止table发生改变
if (tabAt(tab, i) == f) {
// fh>=0,证明是正常的节点插入,不需要考虑红黑树
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果在链表中找到值为key的节点e,直接设置e.val = value即可。
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 如果没有找到值为key的节点,直接新建Node并加入链表即可。
Node<K,V> pred = e;
// put到末尾节点
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果首节点为TreeBin类型,说明为红黑树结构,执行putTreeVal操作。
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).