目录
2、Node / CounterCell / ForwardingNode / ReservationNode
ConcurrentHashMap表示一个线程安全的,基于分段锁实现的Map,其底层数据结构跟HashMap是一致的,都是数组+链表或者红黑树,即数组元素是链表头或者红黑树的根节点,分段锁就是对这里的数组元素加锁,而且是直接使用synchronized关键字加锁,本篇博客就详细分析该类的实现细节。
1、定义
ConcurrentHashMap的类继承关系如下:
ConcurrentMap继承自Map,新增的接口如下:
后面会详细介绍相关方法的用途和实现的。该类包含的实例属性如下:
//保存键值对的数组
transient volatile Node<K,V>[] table;
//扩容时使用的,保存扩容后的新的Node数组
private transient volatile Node<K,V>[] nextTable;
//如果table未初始化,sizeCtl记录了初始容量,如果正在初始化则为-1,如果正在扩容则为非-1的一个负值
//如果已经初始化或者扩容完成,则记录了一个阈值,超过该值会触发扩容
private transient volatile int sizeCtl;
//记录尚未分配出去的待转移的数组元素的个数,初始值是扩容前的数组容量,然后逐步减少至0
private transient volatile int transferIndex;
//baseCount和CounterCell两个都是用来记录Map中键值对的个数
//如果cas修改baseCount失败,就会修改当前对应的CounterCell的value
//获取键值对个数时,将两者累加起来
private transient volatile long baseCount;
//创建或者扩容CounterCells时的锁
private transient volatile int cellsBusy;
//每个线程会根据当前线程probe值计算所属的CounterCell,一般高并发下一个线程对应一个CounterCell
private transient volatile CounterCell[] counterCells;
//执行遍历时的视图
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
该类包含了多个静态常量,如下:
//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认初始容量
private static final int DEFAULT_CAPACITY = 16;
//数组的最大容量
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//用来估算容量,序列化时使用
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//默认的负载率
private static final float LOAD_FACTOR = 0.75f;
//链表中元素个数超过此值,就需要将其转化成一个红黑树,前提是数组长度大于MIN_TREEIFY_CAPACITY
static final int TREEIFY_THRESHOLD = 8;
//红黑树中节点数低于此值,需要将红黑树转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
//转化成红黑树前会检查数组长度,如果低于此值则扩容
static final int MIN_TREEIFY_CAPACITY = 64;
//数组扩容时,会将老数组分割成若干个段,每个线程处理其中一个段对应的数组元素,将其中包含的节点转移到扩容的新数组中
//分配的段的长度不能低于此值
private static final int MIN_TRANSFER_STRIDE = 16;
//扩容时用来计算一个类似时间戳的东西,结果是一个负值,表示正在扩容的过程中
private static int RESIZE_STAMP_BITS = 16;
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//特殊的hash值,正常的hash值都是大于0
static final int MOVED = -1; //ForwardingNode使用的hash值
static final int TREEBIN = -2; //红黑树根节点的hash值
static final int RESERVED = -3; // hash for transient reservations
//用来计算key的hash值,保证算出来的hash值大于0
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
//获取CPU的个数
static final int NCPU = Runtime.getRuntime().availableProcessors();
/** 序列化使用的*/
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("segments", Segment[].class),
new ObjectStreamField("segmentMask", Integer.TYPE),
new ObjectStreamField("segmentShift", Integer.TYPE)
};
除此之外,还有多个表示属性偏移量的静态字段,通过static代码块初始化,如下:
其中ASHIFT表示一个数组元素占的字节数取2的对数,64位下对象数组一个数组元素8个字节,ASHIFT的值是3,ABASE表示数组本身用于基于元素类型、长度等信息占用的字节数,根据这两个可以计算数组某个索引对应的数组元素的偏移量。
2、Node / CounterCell / ForwardingNode / ReservationNode
Node和CounterCell都是两个内部类,前者表示Map中的一个键值对,后者用来计数的,两者的定义如下:
//Contended注解可避免两个CounterCell实例在同一个高速缓存行中导致伪共享的问题
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //key的hash值
final K key;
volatile V val;
volatile Node<K,V> next; //链表的下一个节点
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null && //key不为null
(v = e.getValue()) != null && //value不为null
(k == key || k.equals(key)) && //两者key一致
(v == (u = val) || v.equals(u))); //两者val一致
}
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
//从当前节点往后遍历,找到键值都相等的节点
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
ForwardingNode和ReservationNode都继承自Node,前者表示一个因为扩容而正在移动中的节点,后者表示一个空节点,加锁时使用,其定义如下:
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
//hash值是MOVED
super(MOVED, null, null, null);
this.nextTable = tab;
}
Node<K,V> find(int h, Object k) {
//注意此时遍历的是扩容后的新数组nextTable,如果数组元素是ForwardingNode表示原来的数组元素中的节点全部转移到新数组中去了,可以在新数组中查找
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null) //如果对应索引的数组元素为空
return null;
//e为根据h计算出来的数组索引的元素
for (;;) {
int eh; K ek;
//如果hash值和key都匹配则认为匹配成功
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
if (eh < 0) { //正常节点的hash值都是大于0的
if (e instanceof ForwardingNode) {
//如果e是ForwardingNode,则更新查找的tab,外层的for循环继续,e会更新
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
//其他的特殊节点,在e所在的链表中查找
return e.find(h, k);