怎么实现一个线程安全的hashmap?

1 个回答

  • 小五
    有些东西不需要答案,态度就是答案。
    思路分三档:**直接用库**、**用锁包装**、**自己实现(分段/条带锁 or COW)**。按需选择就行。 # 0)首选:直接用库 **`ConcurrentHashMap<K,V>`** * JDK 官方并发 Map,**无全表锁**,读几乎无锁,写用 **CAS + 局部同步**,迭代“弱一致”。 * 常用原子方法:`putIfAbsent`、`computeIfAbsent`、`compute`、`merge`。 * 注意:**组合操作也要用原子方法**(别先 `get` 再 `put`)。 ```java ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); int v = map.compute("k", (k, oldV) -> oldV == null ? 1 : oldV + 1); ``` **`Collections.synchronizedMap(new HashMap<>())`** * **整张表加一把锁**,简单但并发度低;遍历时需**手动同步**: ```java Map<String,Integer> m = Collections.synchronizedMap(new HashMap<>()); synchronized (m) { // 迭代必须包在同步块 for (var e : m.entrySet()) {/*...*/} } ``` # 1)条带锁(Striped/Segmented)自实现(示例) 读多写少且想自己做个轻量版,可用**分段桶 + 每段一把锁**:避免全表串行。 ```java import java.util.*; import java.util.concurrent.locks.*; public class StripedMap<K,V> { static final int N_SEG = 16; // 段数(2^n) final List<Map<K,V>> buckets = new ArrayList<>(N_SEG); final ReentrantLock[] locks = new ReentrantLock[N_SEG]; public StripedMap() { for (int i = 0; i < N_SEG; i++) { buckets.add(new HashMap<>()); // 也可换成数组+链表结构 locks[i] = new ReentrantLock(); } } private int idx(Object k) { return (k == null ? 0 : k.hashCode()) & (N_SEG-1); } public V get(K k) { int i = idx(k); // 读不加锁是有风险的(HashMap 非线程安全),保险起见加读锁或改为 CHM。 locks[i].lock(); try { return buckets.get(i).get(k); } finally { locks[i].unlock(); } } public V put(K k, V v) { int i = idx(k); locks[i].lock(); try { return buckets.get(i).put(k, v); } finally { locks[i].unlock(); } } public V computeIfAbsent(K k, java.util.function.Function<K,V> f) { int i = idx(k); locks[i].lock(); try { Map<K,V> m = buckets.get(i); V val = m.get(k); if (val == null) { val = f.apply(k); m.put(k, val); } return val; } finally { locks[i].unlock(); } } } ``` **要点/改进** * 真正要高并发读:把段内容器也换成并发结构(或用 **读写锁**:`ReentrantReadWriteLock`)。 * **扩容**:可做“分段扩容”或“停写-整体扩容”(停写期间阻塞写)。 * 内存可见性:段表、桶数组用 `final`,必要字段用 `volatile`。 # 2)Copy-On-Write(读多写极少) 把整个 `Map` 包在 **`AtomicReference<Map<K,V>>`**,写时拷贝新 Map,换引用;读无锁、写开销大。 ```java import java.util.concurrent.atomic.AtomicReference; public class CowMap<K,V> { private final AtomicReference<Map<K,V>> ref = new AtomicReference<>(Map.of()); public V get(K k) { return ref.get().get(k); } public void put(K k, V v) { while (true) { Map<K,V> oldM = ref.get(); Map<K,V> newM = new HashMap<>(oldM); newM.put(k, v); if (ref.compareAndSet(oldM, Collections.unmodifiableMap(newM))) return; } } } ``` **场景**:配置表、黑名单、路由——更新不频繁、读非常多。 ## 常见坑 & 实战建议 * **不要捕获 `ConcurrentModificationException` 来“保证线程安全”**——那只是 fail-fast 迭代器的告警。 * **组合操作用原子方法**(例如统计、初始化缓存):`computeIfAbsent/merge/compute`。 * **大小/遍历**:并发 Map 的 `size()` 不是严格实时值;需要精确快照请加外部同步或复制一份视图。 * **优先选 `ConcurrentHashMap`**:自己造轮子很容易在扩容、可见性、迭代一致性上踩坑。 > 面试/生产结论:**直接用 `ConcurrentHashMap`**;特殊读多写少用 **COW**;想练习可写 **Striped/Segmented** 版本,但要交代清楚扩容与可见性设计。
    发布于 2025-08-13 04:45 •IP属地 甘肃
    举报
    评论
    收藏