源码分析(JDK1.8)
底层数据结构 数组+链表+红黑树(数组大小是2的幂次方)
实现原理 采用CAS和synchronized关键字
注:ConcurrentHashMap的hash算法、求桶索引与HashMap类似,不再赘述。
数据结构 value和next是volatile,保证可见性
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
属性
//默认并发量
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//存储node的数组,第一次插入元素时初始化
transient volatile Node<K,V>[] table;
//扩容时使用,其他情况下为null
private transient volatile Node<K,V>[] nextTable;
//控制table的操作
//-1 正在初始化
//-N 有 N-1个线程正在扩容
//正数:若未初始化表示需要初始化的大小,若已初始化表示table的容量
private transient volatile int sizeCtl;
构造方法
当不指定concurrencyLevel时,默认为1
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, LOAD_FACTOR, 1);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
//sizeCtl调整为大于容量的最小的2的幂次方
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
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
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
table初始化
在调用put方法时会先判断table是否为空
if (tab == null || (n = tab.length) == 0)
tab = 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(); //当前线程让出cpu
else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {//原子操作,将sizeCtl设置为-1,表示正在初始化
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;
sc = n - (n >>> 2);//0.75*capacity
}
} finally {
sizeCtl = sc;//初始化完毕,设置容量
}
break;
}
}
return tab;
}
put操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key value不允许为空
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//当桶中为空时
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // CAS,将元素加入桶中
}
else if ((fh = f.hash) == MOVED) // 当前Map在扩容
tab = helpTransfer(tab, f);
else if (onlyIfAbsent // check first node without acquiring lock
&& fh == hash
&& ((fk = f.key) == key || (fk != null && key.equals(fk)))
&& (fv = f.val) != null)
return fv;
else {//发生hash冲突
V oldVal = null;
synchronized (f) {//加锁,桶中头结点
if (tabAt(tab, i) == f) {
if (fh >= 0) {//链表节点
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value);
break;
}
}
}
else if (f instanceof TreeBin) {//树节点
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {//链表转为树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);//计算节点数,判断是否扩容
return null;
}
总结:
当桶为空时,通过CAS操作加入元素
当桶中非空时,对头结点加锁加入元素
JDK1.7
将数据分成段式存储(Segment<K,V>),Segment实现了ReentrantLock,实现加锁解锁。ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素。对HashEntry进行修改时,需要获取所属的Segment的锁。(底层数据结构使用数组+链表)
ConcurrentHashMap与HashMap
HashMap允许key和value为null
ConcurrentHashMap不允许key和value为null
ConcurrentHashMap与HashTable
HashTable属于全表锁,对表的操作是阻塞的。迭代器具有强一致性,某个线程对表的修改一定会被其他线程获取。
ConcurrentHashMap是非阻塞的,只会锁部分数据。迭代器具有弱一致性。