/* ---------------- Constants -------------- */
/**
* HashMap的最大容量
*/
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;
/**
* 链表升级成红黑树的阈值
*/
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;
//这个常量是用来计算HashMap不同容量有不同的resizeStamp用的
private static int RESIZE_STAMP_BITS = 16;
//最大参与扩容的线程数 相当大的一个数 基本上是不会触及该上线的
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
//要对resizeStamp进行位移运算的一个敞亮
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//特殊的节点哈希值
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
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
//获取CPU的数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
其中,因为JDK8中ConcurrentHashMap的实现方式和JDK7的不同,因此DEFAULT_CONCURRENCY_LEVEL已经没有实际作用了。并且在JDK8中,LOAD_FACTOR也已经固定成了0.75f。
另外,MOVED,TREEBIN,RESERVED是用来表示特殊节点的哈希值。该类特殊节点均不含实际元素,且其哈希值被设置为负数和普通节点区分。
剩下的涉及扩容的常量我们在相关的章节中再介绍。
成员变量
通常成员变量都是会负责记录当前类的状态的,ConcurrentHashMap也是如此。因此了解清除成员变量的作用,对我们后续分析ConcurrentHashMap的操作流程很有感帮助。
/* ---------------- Fields -------------- */
/**
* 底层数组
*/
transient volatile Node<K,V>[] table;
//扩容时 使用的另一个数组
private transient volatile Node<K,V>[] nextTable;
//统计size的一部分
private transient volatile long baseCount;
/**
* sizeCtl与table的resize和init有关
* sizeCtl = -1时,表示table正在init
* sizeCtl < 0 且不等于-1时,表示正在resize
* sizeCtl > 0 时,表示下次需要resize的阈值,即capacity * loadfactory
*/
private transient volatile int sizeCtl;
//记录下一次要transfer对应的Index
private transient volatile int transferIndex;
//表示是否有线程正在修改CounterCells
private transient volatile int cellsBusy;
//用来统计size
private transient volatile CounterCell[] counterCells;
// views
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
同样的,成员变量也可以按作用分成几类:
- 用作底层数据结构的实现:Node<K,V>[] table
- 用作统计元素的大小:baseCount、CounterCell[] counterCells、cellsBusy
- 用作记录ConcurrentHashMap的状态:sizeCtl
- 用作扩容记录:Node<K,V>[] nextTable、transferIndex
- 用作转成其它类型的视图:KeySetView<K,V> keySet、ValuesView<K,V> values、EntrySetView<K,V> entrySet
table数组很好理解。因为HashMap的实现是基于数组的,在冲突时通过链地址解决。因此所有的数据都以数组为入口。
另一个数组nextTable是在扩容时备用的。 如果了解Redis的数据结构的读者,应该对这个不陌生,redis渐进式rehash就是通过两个哈希表加一个index实现的。而JDK8中在resize时,也采取了类似的方式(下文我们会介绍到:按步长逐步transfer)。
另外,比较重要的一个属性就是sizeCtl。
如果看过Doug Lea老爷子在JUC下的其他类,经常会有一个特殊的变量表示当前对象的状态。并且已CAS的方式去修改这个变量,实现自旋锁的功能(例如:AQS中的state)。
这里的sizeCtl就是一个富有特殊意义的变量。
当sizeCtl大于0时,表示扩容的阈值(没错,就是HashMap中threshold变量的作用),而且上文我们也了解到在JDK8中由于loadfactor已经被固定为0.75f。因此在正常状态下(非扩容状态),sizeCtl = oldCap >> 1 - (oldCap << 2)。 而sizeCtl == -1是一个特殊的状态标志,表示ConcurrentHashMap正在初始化底层数组。
当sizeCtl为其他负数时,表示ConcurrentHashMap正在进程扩容,其中,高16位可以反应出扩容前数组的大小,而后16位可以反应出此时参与扩容的线程数。
内部类
ConcurrentHashMap拥有大量的内部类,但其中大部分都是用来遍历或是在Fork/Join框架中平行遍历时使用的。这部分类内部类我们不在过多介绍。主要看CountCell和几个Node的类。
CounterCell
首先,CounterCell是用来统计ConcurrentHashMap用的,其内部有个v