ConcurrentHashMap概述
在JDK1.8中ConcurrentHashMap的实现方式或者说实现思想相比之前的版本已经相差了很大了,在JDK1.8之前ConcurrentHashMap是采用的是Segment(分段锁)的方式,而JDK1.8中的ConcurrentHashMap则摒弃了原来的做法,同时沿用了JDK1.8中的HashMap的数组+链表+红黑树的方式,同时大量的使用了java并发包作者非常多用的Unsafe里面的方法,也就是利用CAS的思想来实现,虽然已网上已经有了很多优秀的分析,但是为了加深印象还是自己写一下。
数据构成
ConcurrentHashMap与HashMap的内部数据结构与HashMap的思想基本一致,都是使用的数组+链表+红黑树的,但是相比之下ConcurrentHashMap也有一些改动,比如ConcurrentHashMap的树结构并不是像HashMap那样直接链上TreeNode节点而是在内部定义了一个TreeBin的类来作为容器去包含了这些树节点,也就是说实际存放在数组上的对象是TreeBin而不是TreeNode,同时TreeNode也承担了红黑树的构造的工作。
并发
ConcurrentHashMap的数据的插入与删除等的基本流程和HashMap基本一致,在线程安全方面ConcurrentHashMap使用了CAS方式加上synchronized关键字而synchronizez使用的锁则是当前数组中需要加入的位置对应的数组元素,也就是“动哪儿锁哪儿”。所以并发上面性能也很好。
在HashMap中有一个很头疼的resize扩容很慢的问题也得到了解决,ConcurrentHashMap允许多个线程并发的去进行resize操作,这样大大的提高了效率。
源码解析
由于许多内容是和HashMap相同的,说明的会适当的简略一些。
重要的属性
//多线程扩容的时候单个线程负责的table中的元素个数
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;
static final int MOVED = -1; // hash for forwarding nodes ForwardNode的hash值
static final int TREEBIN = -2; // hash for roots of trees //TreeBin节点的的hash值
static final int RESERVED = -3; // hash for transient reservations //ReservationNode节点的的hash值
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash //用于计算hash值
//获取可用的CPU数
static final int NCPU = Runtime.getRuntime().availableProcessors();
//数组
transient volatile Node<K,V>[] table;
//只有在进行扩容的时候使用
private transient volatile Node<K,V>[] nextTable;
/* 当是负数的时候表示正在初始化或者resize
* 当是-1的时候表示正在初始化
* -N 表示有N-1个线程正在进行resize
*/
private transient volatile int sizeCtl;
//用于多线程一起扩容的时候记录位置
private transient volatile int transferIndex;
其中的sizeCtl变量的出镜率很高。用于控制并发resize的时候的线程。
节点
Node节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
//这里与HashMap不同的是val和next被定义为了volatile
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; }
//不允许setValue(与HashMap不同)
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)));
}
//定义了find方法用于辅助map.get()方法
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;
}
}
这里与HashMap不同的是使用volatile标识了val和next,是的val和next的读/写具备了原子性,可以配合CAS一起使用。
TreeNode
TreeNode和HashMap基本差不多。
TreeBin
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
//构造红黑树