底层结构
Node 数组 + 链表 + 红黑树
原理
synchronized + CAS
构造方法
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;
}
put 方法
- key 和 value 不能为 null,否则抛出空指针异常
- 计算 hash 值:key.hashCode() 高 16 位与低 16 位异或后将最高位(符号位)置 0,保证其非负,因为 hash 值为负数时有特殊含义:
static final int MOVED = -1; // hash for forwarding nodes static final int TREEBIN = -2; // hash for roots of trees static final int RESERVED = -3; // hash for transient reservations
- 判断数组是否已初始化,若未初始化则调用 initTable() 方法进行初始化,使用了 CAS 操作
- 找到下标处的元素 (下标计算方式为 hash & length - 1)
- 如果下标位置的元素为 null 则直接插入 (CAS)
- 若正在扩容则帮助扩容
- synchronized 锁住下标处的元素
- 链表:使用尾插法插入新节点,若插入后链表长度超过 8 且数组长度 >= 64 则进行转红黑树(第二个条件不满足则进行扩容)
- 红黑树:调用 putTreeVal 方法将节点插入到红黑树中
- 调用 addCount 方法,计数并判断是否需要扩容 (扩容条件好像比 元素数量(插入后) > 容量 * 加载因子(默认0.75) 还要强一点,fullAddCount 后直接 return 了,暂时还没弄清楚);baceCount 和 counterCells 共同计数,获取元素个数时需要将 baseCount 和 counterCells 中各个位置的 value 加起来
扩容
- 若其他线程已经在扩容了则帮助扩容
- 数据迁移过程
- synchronized 锁住下标处的元素
- 链表:找到最后一段新下标相同的小链表,遍历其他元素,按新下标位置构造两条链表 (头插法),然后一次性移过去 (效果与 1.7 相同),最后把老数组上该位置的元素设置为 ForwardingNode 对象
- 红黑树:按新下标构造两条双向链表,若链表长度小于等于 6 则将该链表的节点类型 (TreeNode) 转换为普通链表节点,若链表长度大于 6,如果另一条链表不为空则需要重新树化 (另一条链表为空则就是原来那棵树整个移过去)
- synchronized 锁住下标处的元素
链表转红黑树
与 HashMap 类似,不过数组元素的类型是 TreeBin 而不是 TreeNode,树的根节点作为 TreeBin 的属性,这样根节点的改变就不会对锁造成影响了,因为锁的是 TreeBin 对象