一. ConcurrentHashMap 是什么
在并发编程中,ConcurrentHashMap 是一个经常被使用的数据结构,相比于 Hashtable 以及Collections.synchronizedMap() 来说,ConcurrentHashMap 在线程安全的基础上提供了更好的写并发能力,同时还降低了对读一致性的要求,是 java.util.concurrent 包里面提供的一个线程安全并且高效的 HashMap。
二. ConcurrentHashMap 的不同版本实现
JDK 1.7 中的实现
JDK 1.7 中 的 ConcurrentHashMap 采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个 Map 加锁,分段锁大大提高了高并发环境下的处理能力。
如上图所示,ConcurrentHashMap 底层是由一个 Segment 数组组成的,每个 Segment 元素包含一个 HashEntry 数组,而每个 HashEntry 元素都是一个链表结构的节点。
提到 HashEntry,很容易会联想到 HashMap 中的 Entry,它们有什么区别呢?
来看一下 HashEntry 的代码实现:
static final class HashEntry {
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
}
可以看出,HashEntry 和 HashMap 非常类似,唯一的区别就是其中的核心数据 value 以及 next 都被 volatile 修饰,以此保证了多线程读写过程中对应变量的可见性。
接下来,我们再来看一下 JDK 1.7 中的 ConcurrentHashMap 的核心方法 put 方法 和 get 方法 的实现:
put 方法
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
// 计算key的hash值
int hash = hash(key);
// 根据 hash 值,segmentShift,segmentMask 定位 Segment
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null)
s = ensureSegment(j);
// 将键值对保存到对应的 Segment 中
return s.put(key, hash, value, false);
}
可以看到,首先通过 key 定位到 Segment,之后在对应的 Segment 中才会调用具体的 put 方法,对应 put 的源码如下:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 如果 tryLock 成功,就返回 null
// 否则尝试去获取锁,获取锁失败的情况下,为了节约时间提前去新建或获取 HashEntry
// 如果超过一定次数就强制加锁
HashEntry<K, V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K, V>[] tab = table;
// 根据table数组的长度和hash值计算index下标
int index = (tab.length - 1) & hash;
// 找到table数组在index偏移处链表的头部
HashEntry<K, V> first = entryAt(tab, index);
// 从first开始遍历链表
for (HashEntry<K, V> e = first; ; ) {
if (e != null) {
K k;
// 如果key相同
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
//