一:字段解析(大部分):
// node数组最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认初始值,必须是2的幕数
private static final int DEFAULT_CAPACITY = 16;
//数组可能最大值,需要与toArray()相关方法关联
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;
// 链表转红黑树阀值,> 8 链表转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
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;
// 2^15-1,help resize的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 32-16=16,sizeCtl中记录size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// forwarding nodes的hash值hash值是-1,表示这是一个forwardNode节点
static final int MOVED = -1;
// 树根节点的hash值 hash值是-2 表示这时一个TreeBin节点
static final int TREEBIN = -2;
// ReservationNode的hash值
static final int RESERVED = -3;
// 可用处理器数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放node的数组
transient volatile Node<K,V>[] table;
/*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
*当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
*当为0时:代表当时的table还没有被初始化
*当为正数时:表示初始化或者下一次进行扩容的大小
private transient volatile int sizeCtl;
二:构造函数解析(一共5种):
涉及到的参数:
initialCapacity:数组的大小,默认为16
sizeCtl: 该属性用来控制table数组的大小,根据是否初始化和是否正在扩容有几种情况:当值为负数时:**如果为-1表示正在初始化,如果为-N则表示当前正有N-1个线程进行扩容操作;当值为正数时:**如果当前数组为null的话表示table在初始化过程中,sizeCtl表示为需要新建数组的长度;若已经初始化了,表示当前数据容器(table数组)可用容量也可以理解成临界值(插入节点数超过了该临界值就需要扩容),具体指为数组的长度n 乘以 加载因子loadFactor;当值为0时,即数组长度为默认初始值。
loadFactor:加载因子
concurrencyLevel:并发度,预估同时操作数据的线程 表示能够同时更新ConccurentHashMap且不产生锁竞争的最大线程数。默认值为16,(即允许16个线程并发可能不会产生竞争)。为了保证并发的性能,我们要很好的估计出concurrencyLevel值,不然要么竞争相当厉害,从而导致线程试图写入当前锁定的段时阻塞。
默认容量是16,但是可以添加的为12,要乘以1/4。
/ 1. 构造一个空的map,即table数组还未初始化,初始化放在第一次插入数据时,默认大小为16
ConcurrentHashMap()
// 2. 给定map的大小
ConcurrentHashMap(int initialCapacity)
// 3. 给定一个map
ConcurrentHashMap(Map<? extends K, ? extends V> m)
// 4. 给定map的大小以及加载因子
ConcurrentHashMap(int initialCapacity, float loadFactor)
// 5. 给定map大小,加载因子以及并发度(预计同时操作数据的线程)
ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
第二种解析:
public ConcurrentHashMap(int initialCapacity) {
//1. 小于0直接抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException();
//2. 判断是否超过了允许的最大值,超过了话则取最大值,否则再对该值进一步处理
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
//3. 赋值给sizeCtl
this.sizeCtl = cap; //此时还未初始化 到插入数据时才进行初始化 所以此时为数组长度,初始化后值应 //*loadFactor加载因子
}
tableSizeFor: 返回一个比给定整数大且最接近的2的幂次方整数,如给定10,返回2的4次方16.
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1; //或运算 只要一个为1就为1,将给定数字的第一个为1的后面位全换为1
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //n+1就是返回一个比次数大的2^n,因为此时 //各位都是1,加1进位
}
调用构造器方法的时候并未构造出table数组,只是算出table数组的长度,当第一次向ConcurrentHashMap插入数据的时候才真正的完成初始化创建table数组的工作。
initTable:初始化数组
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
//保证只有一个线程初始化
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//SIZECTL在static代码块里 //赋值,指向静态变量sizeCTL值
比较的时候用的是sc和SIZECTL,这是因为如果直接使用sizeCTL进行比较的话,由于是volititle进行修饰的变量,在进行比较的时候,永远是最新的,那么这个步骤就永远进行下去,导致并发问题,只有在比较时,在此步骤之前先进行一次赋值给一个变量,用这个变量进行比较,因为在此线程的作用域里这个被赋值的就是进行初始化之前的最新值了,这样比较就可以防止并发问题了。主要是SIZECTL指向的是sizeCTL的class文件的常量池,而且又是volititle变量,因此需要这样做,否则永远都是相等的。
try {
if ((tab = table) == null || tab.length == 0) {
//得出数组的大小
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//这才是真正的初始化数组
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
//计算数组中可用的大小,,实际大小n*loadFactor(0.75)
sc = n - (n >>> 2);//感觉没作用啊,永远都是*0.75
}
} finally {
sizeCtl = sc; //此时这个值就是扩容完的容量了
}
break;
}
}
return tab;
}
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));k是concurrenthashmap的class文件,因此个人认为:SIZECTL就是sizeCTL的值,实时的。因此将此值与获取到的sizeCTL进行比较,来保证加锁。
finally 将sizeCTL赋值为可存储容量后,那么一定是正数,再次进行初始化 就还是这个数目,除非接下来会在进行赋值为1.5倍这种,前面的构造函数的部分内容在进行一次。
三:基本的存储结构类: Node TreeNode TreeBin
-
Node,实现了Map.Entry<K,V> 接口,主要存放key-value对,并且具有next域
static class Node<K,V> implements Map.Entry<K,V> {
final int 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 &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
* 增加find方法辅助get方法 ,HashMap中的Node类中没有此方法
*/
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;
}
}
TreeNode,树节点,继承于承载数据的Node类。而红黑树的操作是针对TreeBin类的,从该类的注释也可以看出,也就是TreeBin会将TreeNode进行再一次封装
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next,
TreeNode<K,V> parent) {
super(hash, key, val, next);
this.parent = parent;
}
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
/**
* Returns the TreeNode (or null if not found) for the given key
* starting at given root.
* 根据给定的key值从root节点出发找出节点
*/
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk; TreeNode<K,V> q;
TreeNode<K,V> pl = p.left, pr = p.right;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
}
return null;
}
}
TreeBin 这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。实际的 ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象。
static final class TreeBin<K,V> extends Node<K,V> { //还有很多的关于树的方法就不贴出来,在下面涉及时进行分析
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock 此树已经有获取到锁了
static final int WAITER = 2; // set when waiting for write lock 此树等待获取锁,没人用
static final int READER = 4; // increment value for setting read lock 增加读锁 嗲表有几个线程在读
}