0

0

在Java中ConcurrentHashMap如何保证安全_Java并发映射机制解析

P粉602998670

P粉602998670

发布时间:2026-01-17 12:08:02

|

299人浏览过

|

来源于php中文网

原创

concurrenthashmap线程安全靠分段锁(jdk7)或cas+synchronized单节点锁(jdk8+),get()无锁,put()仅锁桶头,size()非o(1)且有误差,迭代器弱一致性,foreach()不支持遍历时修改,computeifabsent()可能重复初始化,扩容时get()可能读到旧值。

在java中concurrenthashmap如何保证安全_java并发映射机制解析

ConcurrentHashMap 的线程安全不是靠 synchronized 全局锁

它用的是分段锁(JDK 7)或 CAS + synchronized 锁单个 Node(JDK 8+),只锁哈希桶链表/红黑树的头节点,而不是整个 Map。这意味着多个线程可以同时读写不同桶的数据,吞吐量远高于 HashtableCollections.synchronizedMap()

常见误解是“它内部全加了锁”,实际在 JDK 8 中:get() 完全无锁(依赖 volatile 语义),put() 只在必要时对桶首节点加 synchronized,且锁粒度极小。

  • size() 不是 O(1),而是遍历所有 CounterCell 累加,可能有短暂误差(最终一致)
  • containsKey()get() 行为一致,都无锁、不阻塞
  • 迭代器弱一致性:不抛 ConcurrentModificationException,但可能漏掉或重复看到刚插入/删除的元素

为什么不能用 forEach() 替代 for-loop 遍历 ConcurrentHashMap

forEach()ConcurrentHashMap 自带的并行遍历方法,底层调用 Traverser 分段扫描,但它的行为和传统 for-each 循环(即 entrySet().iterator())有本质区别:

  • entrySet().iterator() 返回的是弱一致性迭代器,遍历时允许并发修改,但不保证看到最新状态
  • forEach(BiConsumer) 在遍历中若其他线程修改了正在访问的桶,该次遍历仍会继续,但不会反映那些修改;它不阻塞也不重试
  • 更关键的是:forEach() 无法在遍历中安全地执行 remove()computeIfAbsent() —— 这些操作会引发 IllegalStateException 或不可预期结果

真正需要边遍历边更新时,应改用 replaceAll()computeIfPresent() 等原子方法,或先收集 key 再批量处理。

立即学习Java免费学习笔记(深入)”;

computeIfAbsent() 在高并发下可能重复初始化

computeIfAbsent() 声称“只计算一次”,但前提是传入的 mappingFunction 是幂等的。它底层逻辑是:先 get(),没命中再加锁、再次检查、再调用函数。两次检查之间存在窗口,若 mappingFunction 创建对象开销大或有副作用,就可能被多个线程同时触发。

VIVA
VIVA

一个免费的AI创意视觉设计平台

下载

典型错误场景:

map.computeIfAbsent(key, k -> new ExpensiveObject(k)); // 可能创建多个实例

解决方式:

  • 确保 mappingFunction 本身轻量且无副作用
  • 若必须延迟初始化重型资源,改用双重检查 + putIfAbsent() 组合
  • JDK 9+ 提供了 computeIfAbsent(key, mappingFunction) 的替代思路:用 ConcurrentHashMap 配合 FutureTask 缓存初始化任务

扩容时 get() 为什么不会阻塞,但可能看到旧值

ConcurrentHashMap 扩容是渐进式迁移(JDK 8 中通过 transfer() 方法),由首个触发扩容的线程启动,后续 put/get 线程协助迁移。关键点在于:get() 遇到正在迁移的桶(即 ForwardingNode),会直接跳转到新表查询。

但这个过程不是原子切换:

  • 旧表中部分桶已迁走,部分还没动
  • 如果一个 key 在旧表中尚未迁移,get() 会从旧表读到旧值;若此时新表已写入新值,旧值就是“过期”的
  • 这种不一致仅存在于迁移窗口期(通常很短),属于最终一致性范畴,不是 bug

所以不要依赖 get() 立即看到 put() 后的最新值——这和 volatile 字段类似,只能保证可见性,不保证实时性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

266

2025.12.04

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

75

2025.10.23

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

77

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

40

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

67

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

47

2025.11.27

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

9

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

22

2026.03.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号