ConcurrentHashMap
是 Java 中一个线程安全的哈希表实现,它允许多个线程并发地读取和写入映射。与 Hashtable
不同,ConcurrentHashMap
使用了一种分段锁(Segment Lock)机制来提高并发性能。
以下是 ConcurrentHashMap
的源码解读和详解:
1. 类定义和成员变量
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable {
private static final long serialVersionUID = 7249069246763182397L;
// 默认初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 并发级别,即段的数量
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 用于存储节点的数组
transient volatile Node<K, V>[] table;
// 访问计数器,用于触发重散列操作
private transient volatile long baseCount;
// 其他成员变量...
}
2. 构造函数
ConcurrentHashMap
提供了多个构造函数,可以指定初始容量、负载因子和并发级别。
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
// 初始化代码...
}
3. 核心方法
3.1 put
方法
put
方法是插入或更新键值对的核心方法。
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
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;
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, null)))
break; // no lock when adding to empty bin
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
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, 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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
3.2 get
方法
get
方法是获取键对应的值的核心方法。
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;
}
4. 辅助方法
4.1 spread
方法
spread
方法用于将哈希码的高16位与低16位进行异或运算,以减少哈希冲突。
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
4.2 initTable
方法
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(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
5. 总结
ConcurrentHashMap
通过分段锁机制和高效的哈希算法,实现了高并发环境下的高效读写操作。其核心在于通过细粒度的锁控制和无锁读操作,提高了并发性能。同时,通过重散列和树化等机制,保证了在高并发情况下的性能稳定性。