1. 前言2. 简介3. 成员变量4. 构造方法5. 内部类
1. 前言HashMap不是线程安全的,在处理并发的时候可能会出现问题。而HashTable虽然是线程安全的,但是却是需要所有操作竞争同一把锁,效率自然会下降很多。而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问不同段的数据时,就不会出现锁竞争了,进而可以有效提高并发效率。这就是ConcurrentHashMap采用的分段锁思想。
2. 简介ConcurrentHashMap是HashMap的线程安全版本,内部也是采用数组+链表+红黑树存储元素的,因为要尽可能保持并发的效率,所以代码会更复杂,成员变量也有很多不同,这些都是为了并发的效率考虑的,ConcurrentHashMap的并发效率比简单粗暴的HashTable会高很多。
3. 成员变量// 最大表容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认表容量(16), 注:表的容量必须是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时,会将红黑树转化为链表
static final int UNTREEIFY_THRESHOLD = 6;
// 这个记录的是树化的条件之一,当表的长度64,就具备了树化的条件之一
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=-1,表示当前结点是FWD(forwarding)结点(该处结点已经被迁移)
static final int MOVED = -1; // hash for forwarding nodes
// 当node的hash=-2,表示当前结点已经树化,且当前结点是TreeBin结点(TreeBin结点代理操作红黑树)
static final int TREEBIN = -2; // hash for roots of trees
// node的hash=-3,临时保留的hash值,暂时用不到
static final int RESERVED = -3; // hash for transient reservations
// 0x7fffffff转化为2进制就是1111111111111111111111111111111(31个1)
// 在计算哈希值时会用到
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
// 当前系统的cpu数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
// 用来存储表中的数据,ConcurrentHashMap底层就是Node数组
transient volatile Node[] table;
// 在数组扩容时需要用到的表,表示新表,扩容之后将新表赋给table,nextTable重新设置为null
private transient volatile Node[] nextTable;
// 表示列表中的元素数量
private transient volatile long baseCount;
// -1 :当前有线程正在初始化table数组
// -N :高16位表示扩容的版本号,低16位表示扩容线程数(1 + n threads),n表示当前参与扩容的线程数
// =0 :在调用无参构造方法创建ConcurrentHashMap对象时,不会给sizeCtl赋值,默认为0,这样第一次初始化时表的容量为16
// >0 :
// 如果table未初始化,表示初始化时表的容量大小
// 如果table已经初始化,表示下一次扩容时表应该达到的阈值
private transient volatile int sizeCtl;
private transient volatile int transferIndex;
// 表示counterCells数组当前有无线程加锁
private transient volatile int cellsBusy;
// 可以对比LongAdder为了降低线程冲突创建的cells数组
// 因为baseCount表示表中元素数量,当有多个线程通知修改
// baseCount时,只会有一个线程会成功,冲突率太高,通过
// 创建数组分散热点,可以很好地减小冲突发生的概率,时间换空间
// 我写过一篇LongAdder的解析,大家可以去看一看啊~
private transient volatile CounterCell[] counterCells;
与HashMap相比,ConcurrentHashMap使用sizeCtl来控制扩容时需要达到的阈值
-1 :当前有线程正在初始化table数组
-N :高16位表示扩容的版本号,低16位表示扩容线程数(1 + n threads),n表示当前参与扩容的线程数
=0 :在调用无参构造方法创建ConcurrentHashMap对象时,不会给sizeCtl赋值,默认为0,这样第一次初始化时表的容量为16
**>**0 :
如果table未初始化,表示初始化时表的容量大小如果table已经初始化,表示下一次扩容时表应该达到的阈值 4. 构造方法
// 无参的构造方法,sizeCtl未赋值,默认为0,如果调用这个构造方法创建对象,则第一次扩容时table的容量会是16
public ConcurrentHashMap() {
}
// 传入一个指定容量,虽然传入了指定容量,但实际上初始化时的容量是最小的大于等于这个数的2的次幂
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
// 下面这个方法会计算最小的大于等于initialCapacity*1.5+1的2的次幂
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
// 设置sizeCtl为计算出的cap,表示新建数组时需要new的数组的大小
this.sizeCtl = cap;
}
public ConcurrentHashMap(Map extends K, ? extends V> m) {
// 设置sizeCtl为默认容量DEFAULT_CAPACITY(16)
this.sizeCtl = DEFAULT_CAPACITY;
// 调用这个方法将传的Map集合插入列表中
putAll(m);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
// 调用另一个构造方法
this(initialCapacity, loadFactor, 1);
}
// 指定初始容量,负载因子,和并发级别
// 虽然指定了初始容量,但是真正初始化时的容量确是最小的大于等于1+指定初始容量/负载因子的2的次幂
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
// 负载因子小于等于0或初始容量小于0或并发级别小于0,直接抛出异常
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
// 计算1+初始容量/负载因子的大小
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
// 计算最小的大于等于1+初始容量/负载因子的2的次幂
MAXIMUM_CAPACITY : tableSizeFor((int)size);
// 设置sizeCtl为计算出的cap,表示创建数组时需要new的数组的大小
this.sizeCtl = cap;
}
5. 内部类
Node结点
这个节点类型就是我们存储到ConcurrentHashMap中的基本类型
// Node结点 static class Nodeimplements Map.Entry { final int hash; // 哈希值 final K key; // 键 volatile V val; // 值 volatile Node next; // 下一个Node结点 Node(int hash, K key, V val, Node 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))); } Node find(int h, Object k) { Node 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结点
这个结点在扩容的时候会用到。
// ForwardingNode结点 // 如果是一个写的线程(并发扩容),则需要为创建新表贡献一份力 // 如果是一个读的线程,则调用内部类的find(int h, Object k)方法读出数据 static final class ForwardingNodeextends Node { // 表示新表的引用 final Node [] nextTable; ForwardingNode(Node [] tab) { super(MOVED, null, null, null); this.nextTable = tab; } // 到新表上读数据 Node find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node [] tab = nextTable;;) { Node e; int n; if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; for (;;) { int eh; K ek; if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; if (eh < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode )e).nextTable; continue outer; } else return e.find(h, k); } if ((e = e.next) == null) return null; } } } }
TreeNode结点
表示红黑树上的结点。
static final class TreeNodeextends Node { // 父节点 TreeNode parent; // red-black tree links // 左子节点 TreeNode left; // 右子节点 TreeNode right; // 前驱结点 TreeNode prev; // needed to unlink next upon deletion // 颜色 boolean red; TreeNode(int hash, K key, V val, Node next, TreeNode parent) { super(hash, key, val, next); this.parent = parent; } Node find(int h, Object k) { return findTreeNode(h, k, null); } final TreeNode findTreeNode(int h, Object k, Class> kc) { if (k != null) { TreeNode p = this; do { int ph, dir; K pk; TreeNode q; TreeNode 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结点
代为管理红黑树
static final class TreeBinextends Node { TreeNode root; volatile TreeNode 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 }



