首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【集合框架ConcurrentHashMap初级】

【集合框架ConcurrentHashMap初级】

作者头像
艾伦耶格尔
发布2025-08-28 15:56:39
发布2025-08-28 15:56:39
2790
举报
文章被收录于专栏:Java基础Java基础

一、ConcurrentHashMap:高并发场景下的线程安全地图

想象一下,你正在经营一个大型图书馆,每天有成千上万的读者同时来借书、还书、查书。如果只有一个管理员,那肯定忙不过来——这就是并发问题。 而 ConcurrentHashMap 就像给图书馆配备了智能分区管理系统:每个区域有独立管理员(锁),读者可以同时在不同区域活动,互不干扰,效率极高。


1. 为什么需要 ConcurrentHashMap?

在 Java 中,HashMap 是最常用的数据结构之一,但它有一个致命缺点:它不是线程安全的

❌ 问题场景:多线程下的 HashMap
代码语言:javascript
复制
Map<String, Integer> map = new HashMap<>();
// 10个线程同时 put
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
    executor.submit(() -> map.put("key" + i, i));
}

在 JDK 1.7 中,这可能导致死循环(扩容时链表成环);在 JDK 1.8 中虽然修复了死循环,但仍可能数据丢失或覆盖

✅ 解决方案:ConcurrentHashMap

它专为高并发读写设计,既能保证线程安全,又不会像 Hashtable 那样“一锁锁全表”,性能极低。


2. ConcurrentHashMap vs HashMap vs Hashtable

特性

HashMap

Hashtable

ConcurrentHashMap

线程安全

✅(全表锁)

✅(细粒度锁)

性能

允许 null 键/值

实现方式

数组 + 链表/红黑树

同左 + synchronized

CAS + synchronized + volatile

适用场景

单线程

已淘汰

多线程高并发

⚠️ 为什么不允许 null? 避免 get(key) 返回 null 时无法判断是“没找到”还是“值就是 null”,在并发环境下容易出错。


二、JDK 1.7 vs JDK 1.8 的演进

1. JDK 1.7:分段锁(Segment)

  • 把整个 map 分成多个 Segment(类似小 HashMap)。
  • 每个 Segment 是一个独立的锁(继承 ReentrantLock)。
  • 默认 16 个 Segment,最多支持 16 个线程并发写。
代码语言:javascript
复制
ConcurrentHashMap map = new ConcurrentHashMap();
// 默认 concurrencyLevel = 16

缺点:结构复杂,且 Segment 数量固定,不够灵活。

2. JDK 1.8:CAS + synchronized 桶位锁

  • 放弃 Segment,回归数组结构。
  • 使用 CAS 实现无锁化操作。
  • 写操作时只锁当前桶位(即数组的一个位置),读操作完全无锁。

✅ 优势:结构更简单,锁粒度更小,性能更高。


三、核心概念解析(初学者必看)

1. 什么是 CAS?

CAS(Compare and Swap),即“比较并交换”,是一种无锁的原子操作

🔍 原理:
  • 线程在修改变量前,先读取它的当前值 V
  • 然后计算新值 N
  • 最后用 CAS 指令判断:如果当前值还是 V,就更新为 N;否则失败重试
🧩 类比:

就像你去抢演唱会门票:

  • 你看票价是 500(V)
  • 你想买一张,系统说:如果票价还是 500,就扣款出票;否则提示“已被抢光”
  • 这样就避免了多人同时抢票导致超卖
✅ CAS 的优点:
  • 无锁,性能高
  • 适用于并发不激烈的场景
❌ 缺点:
  • ABA 问题(值从 A→B→A,看起来没变,但中间变了)
  • 高并发下可能自旋重试,消耗 CPU

ConcurrentHashMap 中,CAS 被广泛用于:

  • 初始化 table
  • 插入第一个节点
  • 修改 sizeCtl 等控制变量

2. 什么是 volatile?

volatile 是 Java 的关键字,用于保证变量的可见性禁止指令重排序

🔍 在 ConcurrentHashMap 中的作用:
  • table 数组被声明为 volatile,确保一个线程对它的修改,其他线程能立即看到。
  • 避免因 CPU 缓存不一致导致的读取脏数据。

3. 什么是“桶位锁”?

ConcurrentHashMap 的底层是一个数组,每个位置称为一个“桶”(bucket)。

  • 当线程要修改某个 key 时,先计算它的 hash,找到对应的桶。
  • 然后对这个桶的头节点加 synchronized 锁
  • 其他线程如果操作的是不同桶,完全不受影响。
代码语言:javascript
复制
// 伪代码示意
Node<K,V> f = tabAt(tab, i = (n - 1) & hash); // 找到桶
synchronized (f) { // 只锁这个桶
    // 插入或更新
}

✅ 效果:读不加锁,写只锁一行,并发性能极高。


四、核心原理剖析(JDK 1.8+)

1. 数据结构

代码语言:javascript
复制
transient volatile Node<K,V>[] table;
  • table 是 Node 数组,每个 Node 是链表节点。
  • 当链表长度 ≥ 8 且 table.length >= 64 时,链表转为红黑树(TreeNode),防止查找退化为 O(n)。

2. put 操作流程

  1. 计算 key 的 hash 值
  2. 若 table 为空,用 CAS 初始化
  3. 若对应桶为空,用 CAS 插入新节点
  4. 若桶正在扩容(ForwardingNode),则协助扩容
  5. 否则,对桶头节点加 synchronized 锁,插入或更新
  6. 检查是否需要树化

🔥 核心:只有在哈希冲突时才加锁,且只锁冲突链的头节点


五、常用 API 与使用示例

1. 基本操作

代码语言:javascript
复制
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("visits", 100);
Integer visits = map.get("visits");

2. 原子性更新(推荐)

代码语言:javascript
复制
// 若键不存在,则插入(常用于初始化)
map.putIfAbsent("visits", 0);

// 原子性递增(无锁)
map.compute("visits", (k, v) -> v == null ? 1 : v + 1);

// 合并值
map.merge("users", 10, Integer::sum); // users += 10

// 键不存在时加载(缓存常用)
map.computeIfAbsent("config", this::loadFromDB);

💡 这些方法内部已处理线程安全,比先 get 再 put 更高效、更安全


六、适用场景与最佳实践

✅ 适用场景

  • 高并发缓存(如本地缓存)
  • 计数器(PV/UV、订单统计)
  • 并发任务状态管理
  • 分布式协调中的本地映射

❌ 不适用场景

  • 需要强一致性的全局操作(考虑分布式锁)
  • 存储 null 值
  • 低并发环境(直接用 HashMap 更高效)

💡 最佳实践

合理初始化容量,避免频繁扩容:

代码语言:javascript
复制
new ConcurrentHashMap<>(1024);

优先使用原子方法,如 computeIfAbsent

避免在 synchronized 块中执行耗时操作

监控 size,防止内存泄漏


七、总结

ConcurrentHashMap 是 Java 并发编程的核心利器,它通过:

  • CAS 实现无锁初始化
  • volatile 保证可见性
  • synchronized 桶位锁 实现细粒度并发控制

在保证线程安全的同时,提供了极高的并发性能。

对于初学者来说,理解 CAS、volatile、桶位锁 是掌握它的关键。而对于高级开发者,computemerge 等原子方法的灵活运用,能让代码更简洁、更高效。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、ConcurrentHashMap:高并发场景下的线程安全地图
    • 1. 为什么需要 ConcurrentHashMap?
      • ❌ 问题场景:多线程下的 HashMap
      • ✅ 解决方案:ConcurrentHashMap
    • 2. ConcurrentHashMap vs HashMap vs Hashtable
  • 二、JDK 1.7 vs JDK 1.8 的演进
    • 1. JDK 1.7:分段锁(Segment)
    • 2. JDK 1.8:CAS + synchronized 桶位锁
  • 三、核心概念解析(初学者必看)
    • 1. 什么是 CAS?
      • 🔍 原理:
      • 🧩 类比:
      • ✅ CAS 的优点:
      • ❌ 缺点:
    • 2. 什么是 volatile?
      • 🔍 在 ConcurrentHashMap 中的作用:
    • 3. 什么是“桶位锁”?
  • 四、核心原理剖析(JDK 1.8+)
    • 1. 数据结构
    • 2. put 操作流程
  • 五、常用 API 与使用示例
    • 1. 基本操作
    • 2. 原子性更新(推荐)
  • 六、适用场景与最佳实践
    • ✅ 适用场景
    • ❌ 不适用场景
    • 💡 最佳实践
  • 七、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档