此文章在HashMap的基础上介绍,基于java1.8,主要从结构、并发保证、put数据、get数据、大小统计以及扩容来介绍。
结构
Node节点
Node:链表节点
ForwardingNode:仅在扩容时用到,标识此节点已迁移到新table上且扩容未完成
ReservationNode:保留节点,在compute
和computeIfAbsent
中使用,在正式赋值之前起一个暂时占位的作用。
TreeBin:用于封装维护TreeNode,指向红黑树的根结点,包含红黑树的各种转换操作
TreeNode:红黑树节点
基本变量
transient volatile Node<K,V>[] table;
private transient volatile Node<K,V>[] nextTable;//扩容时使用的临时table
private transient volatile int transferIndex; //扩容时使用的标记下标
private transient volatile int sizeCtl;//用于初始化和扩容
private transient volatile long baseCount; //非并发时数量统计
private transient volatile int cellsBusy; //标志位,0进行赋值,1正在执行赋值
private transient volatile CounterCell[] counterCells; //并发时统计
@sun.misc.Contended //标识着这个类需要防止 "伪共享".
static final class CounterCell {
volatile long value;
CounterCell(long x) {
value = x; }
}
sizeCtl 含义:
- -1:table正在初始化
- -N:表示正有N-1个线程执行扩容操作
高 16 位是 length 生成的标识符
低 16 位是扩容的线程数 - 大于0
如果table已经初始化,代表table容量(默认为table大小的0.75)
如果还未初始化,代表需要初始化的大小
并发保证
主要通过CAS、Synchronized保证线程安全。在node级别加锁,保证多个线程可以同时访问Table中的不同Node。
put数据
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//hashcode,为了寻找下标
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;// i:下标;n:数组长度;f:下标所在Node;fh:f的hashcode
if (tab == null || (n = tab.length) == 0)//初始化
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//下标位置为null直接插入,(n - 1) & hash等于对n取模
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)//寻找下标返回Node表示要进行MOVED操作
tab = helpTransfer(tab, f);
else {
//插入链表或红黑树
V oldVal = null;
synchronized (f) {
//f锁住防止链表成环
if (tabAt(tab, i) == f) {
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)))) {
//找到key,判断是否替换
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;