1. ConcurrentHashMap概述
ConcurrentHashMap是线程安全的哈希表,不同于HashTable,后者在方法上增加synchronized关键字,利用对象同步锁实现线程之间的同步。显然,HashTable实现线程安全的方式太“重”,并发度高的情况下,很多线程争用同一把锁,吞吐量较低。
ConcurrentHashMap通过锁分段技术,只有在同一个段内,才会存在锁竞争,提高了并发处理能力。它的内部数据结构其实是一个Segment数组,该数组的大小代表了ConcurrentHashMap的并发度,Segment同时也是一把可重入锁,该锁用来确保该段数据并发访问的线程安全。每一个Segment其实是一个类似于HashMap的哈希表,用来存储key-value。看下ConcurrentHashMap结构图:
ConcurrentHashMap维护了一个Segment数组segments,每个Segment是一个哈希表。当线程需要访问segments[1]处的哈希表,首先需要获取该段的锁,然后才能访问该段的哈希表。上图中segments数组大小为8,因此并发度为8,最多支持8个线程在不同的段同时访问。
2. HashEntry
HashEntry代表了哈希表的一个key-value项,它是ConcurrentHashMap的一个内部静态类,看下HashEntry的数据结构:
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//……
}
HashEntry数据结构也很简单,它是一个单链表结点,每个结点包括了key-value对、哈希值、指向下一个节点的引用。
3. Segment
ConcurrentHashMap最重要的概念就是Segment了,它是一个有锁功能的(继承了ReentrantLock)哈希表,ConcurrentHashMap正是由Segment数组组成的数据结构。
看下Segment的类声明:
static final class Segment<K,V> extends ReentrantLock implements Serializable
Segment通过继承ReentrantLock拥有了锁的功能。
接着看下Segment的几个成员变量:
//获取锁失败后的尝试次数,和机器可用的cpu核数量有关
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
//哈希表,一个segment对应一个哈希表
transient volatile HashEntry<K,V>[] table;
//哈希表kv元素的个数,注意:ConcurrentHashMap的元素数量是所有segment的元素数量之和
transient int count;
//哈希表改变的次数
transient int modCount;
//哈希表重哈希的阀值,元素数量超过这个值,需要扩充哈希表,否则哈希冲突会增加
transient int threshold;
//加载因子
final float loadFactor;
这几个变量我们之前在学习HashMap的时候基本上都学习过,看下注释就可以了。
看下Segment唯一的一个构造方法:
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
Segment没有默认构造方法。
接着看下Segment的put方法:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
//到这,一定成功获取锁了。
//注意,此时node可能为空,也可能不为空。如果为空,接下来put的时候需要创建一个新的结点,如果不为空
//可以直接使用该节点。
//返回key对应老的value值
V oldValue;
try {
HashEntry<K,V>[] tab = table;
//定位到HashEntry索引
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已经存在,更新对应的value,跳出for循环
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
//node不为空,将node插入到链表的头部
i