Concurrenthashmap实现
外观
概述[编辑 | 编辑源代码]
ConcurrentHashMap是Java集合框架中线程安全的哈希表实现(位于`java.util.concurrent`包),专为解决多线程环境下`HashMap`的线程不安全问题和`Hashtable`的性能瓶颈而设计。其主要特点包括:
- 分段锁技术(JDK 1.7)或CAS+synchronized优化(JDK 1.8+)
- 高并发读操作完全无锁
- 迭代器弱一致性(不会抛出`ConcurrentModificationException`)
版本演进对比[编辑 | 编辑源代码]
版本 | 核心机制 | 锁粒度 |
---|---|---|
JDK 1.7 | 分段锁(Segment) | 段级别 |
JDK 1.8+ | CAS + synchronized | 桶级别(Node) |
实现原理[编辑 | 编辑源代码]
JDK 1.7 分段锁设计[编辑 | 编辑源代码]
通过将数据划分为多个Segment(默认16个),每个Segment独立加锁,不同Segment的操作可并行执行。
JDK 1.8 优化方案[编辑 | 编辑源代码]
改用更细粒度的桶级别锁: 1. 使用CAS实现无锁化插入 2. 仅当哈希冲突时,对链表头节点或红黑树根节点加`synchronized`
// JDK 1.8 putVal方法核心逻辑(简化版)
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)))
break;
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
synchronized (f) {
// 链表或红黑树插入逻辑
}
}
}
addCount(1L, binCount);
return null;
}
关键特性[编辑 | 编辑源代码]
线程安全保证[编辑 | 编辑源代码]
- 写操作:JDK 1.8后使用CAS+`synchronized`双重保障
- 读操作:直接访问volatile修饰的`Node.val`,无锁
- 扩容:协助转移机制(多线程共同完成扩容)
一致性模型[编辑 | 编辑源代码]
- 键值对访问保证happens-before原则
- 迭代器反映创建时的状态(可能不包含后续修改)
性能优化技巧[编辑 | 编辑源代码]
初始化参数建议[编辑 | 编辑源代码]
解析失败 (语法错误): {\displaystyle 初始容量 = \frac{预估元素数量}{并发级别} \times 负载因子(0.75) }
// 推荐初始化方式(预估1000个元素,8线程并发)
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>(1000/8*4/3, 0.75f, 8);
原子复合操作[编辑 | 编辑源代码]
// 线程安全的累加操作
map.compute("counter", (k, v) -> (v == null) ? 1 : v + 1);
// 合并插入示例
map.merge("key", 1, Integer::sum);
典型应用场景[编辑 | 编辑源代码]
缓存系统[编辑 | 编辑源代码]
class CacheManager {
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public Object get(String key) {
return cache.computeIfAbsent(key, k -> {
// 模拟数据库查询
return fetchFromDatabase(k);
});
}
}
计数器组[编辑 | 编辑源代码]
// 多线程事件统计
ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();
void recordEvent(String eventType) {
counters.computeIfAbsent(eventType, k -> new AtomicLong()).incrementAndGet();
}
常见问题[编辑 | 编辑源代码]
为什么不允许null值?[编辑 | 编辑源代码]
- 设计上无法区分「键不存在」与「键映射到null」两种状态
- 避免并发场景下的歧义问题
与Collections.synchronizedMap区别?[编辑 | 编辑源代码]
特性 | ConcurrentHashMap | SynchronizedMap |
---|---|---|
锁粒度 | 桶级别 | 全表锁 |
并发读 | 完全无锁 | 需要获取锁 |
迭代器 | 弱一致性 | 快速失败 |
性能测试数据[编辑 | 编辑源代码]
以下为4线程环境下的吞吐量对比(ops/ms):