Files
interview/questions/03-缓存/ConcurrentHashMap原理.md
yasinshaw 0e46a367c4 refactor: rename files to Chinese and organize by category
Organized 50 interview questions into 12 categories:
- 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc.
- 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理
- 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc.
- 04-消息队列 (1 file): RocketMQ/Kafka
- 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc.
- 06-JVM (1 file): JVM和垃圾回收
- 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc.
- 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮
- 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化
- 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx
- 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性
- 12-面试技巧 (1 file): 面试技巧和职业规划

All files renamed to Chinese for better accessibility and
organized into categorized folders for easier navigation.

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:10:53 +08:00

477 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ConcurrentHashMap 原理
## 问题
1. ConcurrentHashMap 在 JDK 1.7 和 1.8 中的实现有什么区别?
2. ConcurrentHashMap 如何保证线程安全?
3. ConcurrentHashMap 的 size() 方法是如何实现的?
4. ConcurrentHashMap 和 Hashtable、Collections.synchronizedMap 的区别?
5. 在实际项目中如何选择线程安全的 Map
---
## 标准答案
### 1. JDK 1.7 vs 1.8
#### **JDK 1.7:分段锁**
**结构**
```
ConcurrentHashMap
Segment[](分段数组,默认 16 个)
HashEntry[](每个 Segment 有自己的 HashEntry 数组)
HashEntry键值对
```
**特点**
- **分段锁**:每个 Segment 独立加锁ReentrantLock
- **并发度**:默认 16Segment 数量)
- **粒度**Segment 级别
**获取锁流程**
```java
// 1. 计算 Segment 索引
int hash = hash(key.hashCode());
int segmentIndex = (hash >>> segmentShift) & segmentMask;
// 2. 获取 Segment
Segment segment = segments[segmentIndex];
// 3. 加锁
segment.lock();
try {
// 操作 HashEntry[]
} finally {
segment.unlock();
}
```
---
#### **JDK 1.8CAS + synchronized**
**结构**
```
ConcurrentHashMap
Node[] + TreeNode[](数组 + 链表 + 红黑树)
Node / TreeNode
```
**特点**
- **CAS + synchronized**CAS 失败后使用 synchronized
- **粒度**Node 级别(更细)
- **并发度**:理论上无限制(实际受限于数组大小)
**核心改进**
| 特性 | JDK 1.7 | JDK 1.8 |
|------|---------|---------|
| **锁机制** | ReentrantLock分段 | CAS + synchronizedNode 级别) |
| **锁粒度** | Segment 级别 | Node 级别(更细) |
| **并发度** | 默认 16 | 理论无限制 |
| **查询** | 需要加锁(不强一致) | 无锁volatile |
| **红黑树** | 不支持 | 支持(链表长度 ≥ 8 |
---
### 2. JDK 1.8 实现原理
#### **核心数据结构**
```java
public class ConcurrentHashMap<K, V> {
// 数组
transient volatile Node<K,V>[] table;
// 数组(扩容时使用)
private transient volatile Node<K,V>[] nextTable;
// 基础计数器值
private transient volatile long baseCount;
// 控制位sizeCtl < 0初始化或扩容-1正在初始化<-1扩容线程数
private transient volatile int sizeCtl;
}
```
---
#### **Node 节点**
```java
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next; // 链表下一个节点
}
```
**注意**`val``next` 都是 `volatile`,保证可见性。
---
#### **TreeNode红黑树节点**
```java
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left; // 左子节点
TreeNode<K,V> right; // 右子节点
TreeNode<K,V> prev; // 前驱节点
boolean red; // 颜色(红黑树)
}
```
**转换条件**
- 链表 → 红黑树:链表长度 ≥ 8 **且** 数组长度 ≥ 64
- 红黑树 → 链表:红黑树节点数 ≤ 6
---
### 3. 核心 API 源码解析
#### **put() 方法**
```java
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();
// 1. 计算哈希值(扰动函数,减少哈希冲突)
int hash = spread(key.hashCode());
int binCount = 0;
// 2. 无限循环CAS + 重试)
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 2.1 初始化数组(延迟初始化)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 2.2 计算索引位置
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 位置为空CAS 插入
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // CAS 成功,退出
}
// 2.3 扩容中MOVED = -1
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); // 帮助扩容
// 2.4 位置不为空(链表或红黑树)
else {
V oldVal = null;
synchronized (f) { // 加锁Node 级别)
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;
}
}
}
}
// 2.5 链表转红黑树
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i); // TREEIFY_THRESHOLD = 8
if (oldVal != null)
return oldVal;
break;
}
}
}
// 3. 增加元素个数LongAdder
addCount(1L, binCount);
return null;
}
```
---
#### **get() 方法**
```java
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
// 1. 数组不为空且索引位置不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 2. 第一个节点匹配
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 3. 红黑树
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 4. 链表遍历
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
```
**注意**
- **无锁读取**:整个 `get()` 方法无锁
- **volatile 可见性**`Node.val``volatile`,保证读到最新值
---
#### **size() 方法**
**问题**:如何高效统计元素个数?
**方案****LongAdder**(分段计数)
```java
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
// 计算所有 CounterCell 的和
CounterCell[] as = counterCells;
long sum = baseCount;
if (as != null) {
for (CounterCell a : as)
if (a != null)
sum += a.value;
}
return sum;
}
```
**LongAdder 原理**
```
LongAdder
baseCount基础值
CounterCell[](计数器数组,避免 CAS 竞争)
多线程更新时,随机选择一个 CounterCell 更新
统计时baseCount + 所有 CounterCell 的值
```
**为什么高效?**
- 高并发时,多线程更新不同的 CounterCell无竞争
- 统计时才累加(牺牲实时性换取性能)
---
### 4. ConcurrentHashMap vs 其他 Map
#### **对比表**
| 特性 | HashMap | Hashtable | Collections.synchronizedMap | ConcurrentHashMap |
|------|---------|-----------|----------------------------|-------------------|
| **线程安全** | ❌ 否 | ✅ 是 | ✅ 是 | ✅ 是 |
| **锁机制** | 无 | synchronized | synchronized | CAS + synchronized |
| **锁粒度** | - | 整个表 | 整个表 | Node 级别 |
| **并发度** | 无 | 低 | 低 | 高 |
| **迭代器** | Fail-Fast | Fail-Safe | Fail-Safe | Fail-Safe |
| **null 键值** | 允许 | 不允许 | 不允许 | 不允许 |
---
#### **详细对比**
**1. Hashtable不推荐**
```java
// Hashtable 的 put 方法(整个表加锁)
public synchronized V put(K key, V value) {
// ...
}
```
**问题**
- **锁粒度太大**:整个表加锁
- **并发度低**:同一时刻只有一个线程能操作
---
**2. Collections.synchronizedMap()**
```java
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
```
**原理**
```java
// 内部使用 mutexObject加锁
public V put(K key, V value) {
synchronized (mutex) { // 整个 Map 加锁
return m.put(key, value);
}
}
```
**问题**
- **锁粒度太大**:整个 Map 加锁
- **迭代器需要手动加锁**
```java
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 遍历需要手动加锁
synchronized (map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
// ...
}
}
```
---
**3. ConcurrentHashMap推荐**
```java
Map<String, String> map = new ConcurrentHashMap<>();
```
**优点**
- **锁粒度小**Node 级别
- **并发度高**:理论上无限制
- **迭代器无需加锁**Fail-Safe弱一致迭代器
---
### 5. 实际项目应用
#### **场景 1本地缓存**
```java
@Component
public class LocalCache {
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
public void remove(String key) {
cache.remove(key);
}
}
```
---
#### **场景 2计数器**
```java
@Component
public class CounterService {
private final ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();
public void increment(String key) {
counters.computeIfAbsent(key, k -> new LongAdder()).increment();
}
public long get(String key) {
LongAdder adder = counters.get(key);
return adder != null ? adder.sum() : 0;
}
}
```
---
#### **场景 3去重表**
```java
@Component
public class DeduplicationService {
private final ConcurrentHashMap<String, Boolean> dedupTable = new ConcurrentHashMap<>();
public boolean isDuplicate(String id) {
return dedupTable.putIfAbsent(id, true) != null;
}
}
```
---
### 6. 阿里 P7 加分项
**深度理解**
- 理解 `ConcurrentHashMap` 的扩容机制(多线程协同扩容)
- 理解 `LongAdder` 的实现原理Cell 数组 + CAS
- 理解 `ConcurrentHashMap` 的弱一致迭代器
**实战经验**
- 有使用 `ConcurrentHashMap` 解决并发问题的经验
-`ConcurrentHashMap` 性能调优的经验
- 有处理 `ConcurrentHashMap` 相关线上问题的经验
**架构能力**
- 能根据业务特点选择合适的 Map 实现
- 能设计高性能的并发数据结构
- 有分布式缓存的设计经验
**技术选型**
- 了解 `ConcurrentSkipListMap`(跳表实现)
- 了解 Guava 的 `LocalCache`Caffeine
- 能根据场景选择本地缓存或分布式缓存