预备知识
- AQS(AbstractQueuedSynchronizer):提供了一个框架用来构造同步一些工具类比如ReentrantLock、 CopyOnWriteArrayList、 CountDownLatch、Semaphore等。通过维护一个volatile修饰的变量state表示锁的获取情况和一个FIFO队列用来存储线程。
API:
Provides a framework for implementing blocking locks and related
synchronizers (semaphores, events, etc) that rely on
first-in-first-out (FIFO) wait queues. This class is designed to
be a useful basis for most kinds of synchronizers that rely on a
single atomic {@code int} value to represent state.
ConcurrentHashMap
介绍
- ConcurrentHashMap是一个线程安全的HashMap,继承体系如下
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable
JDK8和JDK7的区别
存储结构
- JDK8存储结构采用Node类型数组加链表并通过拉链法解决hash冲突而JDK7基于AQS实现了一个Segment类继承自ReentrantLock,每个Segment对象持有一个HashEntry数组。Node和HashEntry都是实现了Map.Entry接口,只不过名字不同。
并发更新操作的实现
- JDK8通过synchronized+CAS机制进行并发更新,锁对象是数组下标对应的元素持有的Monitor。JDK7继承了AQS里的ReentrantLock进行加锁实现的并发更新。
计算size
- JDK8通过维护一个由volatile修饰的baseCount变量进行计数,以及一个CounterCell类进行记录变化的次数来确定size。JDK7采用延迟计算,在计算过程中会对每个Segment计算至少两次,如果出现数据不一致现象就进行全部加锁最后求得size。
部分源码分析
public V put(K key, V value)
直接调用
putVal(K key, V value, boolean onlyIfAbsent)
public V put(K key, V value) {
return putVal(key, value, false);
}
- JDK8
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 预处理
if (key == null || value == null) throw new NullPointerException();
// 为了分布均匀对key的hashcode再hash
int hash = spread(key.hashCode());
// 根据binCount通过计算链表长度,判断链表是否修改过,如果链表的数量超过设定的阈值,插入完成之后需要变成红黑树
int binCount = 0;
// 初始化tab,死循环直到插入成功
for (Node<K,V>[] tab = table;;) {
// 指向目标节点
Node<K,V> f;
int n, i, fh;
// 延迟加载:如果没有初始化进行初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果数组下标对应的元素为null,通过CAS放到当前数组下标处
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果当前节点的hash值为MOVED常量,则表示这个ConcurrentHashMap正在进行扩容,则当前线程帮助扩容helpTransfer(tab,f)。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 当前数组下标有元素,并且可以正常插入,则根据节点元素类型是红黑树节点还是链表节点然后执行插入
else {
V oldVal = null;
// 加锁,Monitor对象是当前数组下标元素持有的Monitor
synchronized (f) {
// tabAt(tab,i)会通过Unsafe类求出数组下标元素,再次判断确保是否是上面所确定的f
if (tabAt(tab, i) == f) {
// 代表是链表元素,执行插入链表操作
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果已经存在此key,则更新即可
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, null);
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;
}
}
}
}
// 根据binCount判断链表元素个数
if (binCount != 0) {
// 超过阈值,则将链表变成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 将当前维护的baseCount + 1
addCount(1L, binCount);
return null;
}
- JDK7
public V put(K key, V value) {
Segment<K, V> s;
if (value == null) throw new NullPointerException();
int hash = hash(key);
// UNSAFE通过hash找到Segment
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K, V>) UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 加锁,如果加锁成功则执行后续操作,没有竞争到锁就需要自旋直到获取到锁并执行插入操作。
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
V oldValue;
try {
// 找到引用的数组
HashEntry<K,V>[] tab = table;
// 求模计算数组下标
int index = (tab.length - 1) & hash;
// 找到数组第一个元素
HashEntry<K,V> first = entryAt(tab, index);
// 死循环,直到插入成功
for (HashEntry<K,V> e = first;;) {
// 数组对应下标已经存在数值。
if (e != null) {
K k;
// 已经存在此key,进行更新
if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
// 记录每一个Segment中的HashEntry数组中元素的数量
++modCount;
}
break;
}
e = e.next;
// 如果当前数组下标已经存在元素
} else {
// 当前线程可能自旋过(自旋过程可能会返回一个已经初始化的node),所以需要判断node是否为null
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
// 判断是否需要扩容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
// 释放锁
unlock();
}
return oldValue;
}
public V get(Object key)
- JDK8
public V get(Object key) {
Node<K,V>[] tab;
Node<K,V> e, p;
int n, eh;
K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 在红黑树中查找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 在链表中查找
while ((e = e.next) != null) {
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
- JDK7
public V get(Object key) {
Segment<K, V> s; // manually integrate access methods to reduce overhead
HashEntry<K, V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
// 通过UNSAFE获取Segment,通过Segment获取HashEntry数组引用,进而找到指定的key
if ((s = (Segment<K, V>) UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) {
for (HashEntry<K, V> e = (HashEntry<K, V>) UNSAFE.getObjectVolatile
(tab, ((long) (((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
public int size()
- JDK8
通过由
volatile
修饰的baseCount
变量以及CounterCell
对象记录变化的次数求出size()
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
// 获取baseCount
long sum = baseCount;
if (as != null) {
// 循环遍历每一个CountCell中的value
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
- JDK7
通过计算Segment数组中每一个Segment中的modcount(在put时会++modCount)求出sum。但是不止求一次sum,会至少求两次(由
RETRIES_BEFORE_LOCK
决定的),如果两次求出的sum不一致,下次求就会将Segment数组全部加锁,重新求一次。
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment<K, V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
try {
for (; ; ) {
// 超出两次,将Segment全部加锁
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K, V> seg = segmentAt(segments, j);
if (seg != null) {
// 获取Segment对象的modCount
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
// 和上一次求的结果比较
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}
参考
- https://www.jianshu.com/p/e694f1e868ec
- http://penghb.com/2017/10/27/java/concurrentHashMap/
- http://www.importnew.com/26049.html
- https://swenfang.github.io/2018/06/03/Java%208%20ConcurrentHashMap%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/