ConcurrentHashMap详细讲解(java)

news2026/5/17 6:19:34
文章目录前言一、 为什么用ConcurrentHashMap1.1 什么是 ConcurrentHashMap1.2 为什么用ConcurrentHashMap二、 并发和锁的基础知识2.1 缘起硬件的“木桶效应”与 JMM 的诞生2.2 并发编程的三大核心危机2.2.1 可见性问题CPU 缓存引发的“盲区”2.2.2 原子性问题线程切换带来的“半途而废”2.2.3 有序性问题编译优化的“移花接木”2.3 破局Java 提供的底层利器2.3.1 synchronized宏观锁2.3.2 volatile 与 Happens-Before 原则2.3.3 CAS (Compare And Swap - 无锁化原子操作)三、 数据结构进化历程3.1 史前时代数组与链表的“极限拉扯”3.2 第一次进化哈希表数组 链表的诞生3.3 第二次终极进化JDK8红黑树的破局四、 常见面试问题五、 实战使用场景5.1 场景一本地高并发缓存 (Local Cache)5.2 场景二实时数据统计汇总总结前言参考学习视频链接带你透彻理解ConcurrentHashMap最新最全的面试题及解答_哔哩哔哩_bilibili一、 为什么用ConcurrentHashMap1.1 什么是 ConcurrentHashMap简单来说ConcurrentHashMap是 Java 并发包java.util.concurrent中提供的一个线程安全的高效哈希表。它的核心功能和HashMap一样都是用来存储键值对Key-Value数据的但它允许多个线程同时对它进行读写操作而不会把数据搞乱。在单线程的HashMap中key 和 value 都允许为 null。但在ConcurrentHashMap中key 和 value 绝对不能为 null。原因这是为了防范并发环境下的“二义性歧义”问题。如果get(key)返回了 null你根本无法确认是“这个 key 不存在”还是“这个 key 的 value 被设置成了 null”。在单线程中你可以通过containsKey(key)去验证但在多线程中你验证的瞬间数据可能已经被其他线程篡改了。1.2 为什么用ConcurrentHashMap单线程时HashMap跑得飞快。但一旦在多线程情况下就会暴露出数据不一致问题。归根结底这种不一致是由Java 内存模型JMM导致的在宏观上主要表现为两类冲突写-写冲突两个线程同时对同一个位置进行修改导致其中一个线程的数据被无情覆盖数据丢失。写-读冲突一个线程刚刚修改了数据但由于工作内存未及时刷入主内存另一个线程去读的时候拿到的依然是旧数据读到脏数据。为了解决数据不一致问题最直观的解法就是加锁这也是HashMap向ConcurrentHashMap演进的核心推手。然而粗暴的加锁会极大拖累系统性能。因此提升高并发容器性能的关键思路在于尽量减小锁的粒度并尽可能找出可以“无锁化”的操作。在设计时需要对读写操作“分而治之”写操作只要涉及数据的改动必然需要加锁兜底此时架构设计的核心考量就是尽力缩小锁的粒度例如从锁整个 Map 缩小到只锁一个节点。读操作在写操作保证不出错的前提下读操作相对好办。核心在于取舍“是否需要实时读到最新数据”强一致性读写双重加锁绝对安全但性能最差如HashTable。顺序一致性使用volatile关键字不加锁但能保证数据的可见性。弱一致性读完全不加锁容忍极端情况下的微小延迟追求极致性能为了在“安全”与“性能”之间平衡ConcurrentHashMap并没有采用单一的加锁手段而是综合运用了以下三种核心底层方案synchronized用于保证特定代码块的原子性和可见性使操作局部串行化保证有序。volatile轻量级同步机制作用于单个共享变量。它像是一个全网广播既能保证数据的可见性又能禁止指令重排序。CAS (Compare-And-Swap)乐观锁机制利用底层硬件指令实现针对单个共享变量的无锁原子操作。简而言之ConcurrentHashMap正是巧妙地运用这三点解决了HashMap的多线程问题。二、 并发和锁的基础知识2.1 缘起硬件的“木桶效应”与 JMM 的诞生计算机的核心硬件在发展过程中存在巨大的速度鸿沟CPU 最快内存次之I/O 设备硬盘最慢。为了不让 CPU 闲着等数据计算机体系结构、操作系统和编译器分别做出了妥协与优化但这恰恰是并发问题的根源CPU 增加了高速缓存Cache以均衡与内存的速度差异。操作系统增加了进程/线程以分时复用 CPU任务切换均衡 CPU 与 I/O 的差异。编译器优化指令执行次序使得缓存能够得到更合理的利用。为了屏蔽各种硬件和操作系统的内存访问差异让 Java 程序在各平台下都能达到一致的效果Java 内存模型JMMJava Memory Model应运而生。 JMM 规定所有的共享变量都存储在主内存中。每个线程都有自己的工作内存里面保存了被该线程使用的共享变量的副本。线程对共享变量的所有操作读、写都必须在工作内存中进行绝对不能直接读写主内存。不同线程之间也无法直接访问对方工作内存中的变量。这种架构虽然高效但在多线程下直接引爆了并发编程的“三大底层危机”。2.2 并发编程的三大核心危机2.2.1 可见性问题CPU 缓存引发的“盲区”定义一个线程对共享变量的修改另一个线程能够立刻看到这就叫可见性。危机成因在多核心架构下线程 A 运行在 CPU-1 上线程 B 运行在 CPU-2 上。线程 A 修改了工作内存对应 CPU-1 缓存中的变量val但还没来得及同步到主内存此时线程 B 去读val读到的依然是 CPU-2 缓存或主内存中的旧值。这就是 CPU 缓存导致的可见性盲区。2.2.2 原子性问题线程切换带来的“半途而废”定义我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。危机成因高级语言里看似简单的一条语句往往需要多条 CPU 指令才能完成。这就是违背我们直觉的地方。经典场景count 1。在 CPU 层面它分为三步Load把变量count从内存加载到 CPU 寄存器。Add在寄存器中执行1操作。Store将结果写入内存缓存。操作系统的任务切换可以发生在任何一条 CPU 指令执行完之后。如果线程 A 刚做完“加法”还没写回内存就被切换到了线程 B线程 B 拿到的还是旧的count值进行操作。最终两个线程各加了一次结果却只增加了 1。2.2.3 有序性问题编译优化的“移花接木”定义程序执行的顺序按照代码的先后顺序执行。危机成因编译器和处理器为了优化性能会对没有数据依赖性的指令进行重排序。经典场景双重检查锁DCL实现单例模式。instance new Singleton();这行代码底层分为三步分配一块内存空间 M。在内存 M 上初始化 Singleton 对象。将 M 的地址赋值给instance变量。重排序灾难编译器可能将执行顺序优化为1 - 3 - 2。当线程 A 执行完 1 和 3 时instance已经不是 null 了但对象还没初始化。此时如果线程 B 进来判断instance null为 false直接拿走这个半成品对象去使用就会触发空指针异常。2.3 破局Java 提供的底层利器为了解决上述的可见性、原子性和有序性问题JMM 定义了 8 种底层操作lock, unlock, read, load, assign, use, store, write并提供了以下并发控制工具2.3.1 synchronized宏观锁提供了更大范围的原子性保证。在底层它通过字节码指令monitorenter和monitorexit隐式地使用了lock和unlock操作。它不仅保证了被锁定代码块的原子性单线程串行执行同时在解锁前会将变量刷新回主内存从而也保证了可见性。当遇到数组元素为空等无法加锁的场景时它无能为力必须依赖其他手段。2.3.2 volatile 与 Happens-Before 原则volatile是一把轻量级的神兵利器专门针对可见性和有序性可见性强制将修改后的值立刻刷入主存并让其他线程的工作内存缓存失效。注意避坑Java 中数组是对象volatile修饰数组时只能保证数组引用地址的可见性如果数组内部元素发生改变是无法保证可见性的这也是为什么 ConcurrentHashMap 底层需要用 Unsafe 来操作数组元素。有序性禁止重排利用内存屏障防止指令重排。底层约束JMM 定义了 8 条Happens-Before先行发生规则。其中“volatile 变量规则”明确指出对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。2.3.3 CAS (Compare And Swap - 无锁化原子操作)高级语言中除了基本数据类型的直接读写long和double除外其他操作比如new一个对象绝大多数都不是原子操作。CAS是直接调用 CPU 底层硬件提供的一条原子指令cmpxchg。Java 中通过Unsafe类提供的方法如compareAndSwapInt来暴露 CAS 能力。它通过“比较并替换”的机制在不加锁、不阻塞线程仅自旋等待的情况下硬生生地保证了单个共享变量修改的原子性。这也是ConcurrentHashMap在空桶插入时能够实现无锁高并发的核心底气。三、 数据结构进化历程3.1 史前时代数组与链表的“极限拉扯”在最基础的数据结构世界里数组和链表是两个性格迥异的极端数组Array内存中连续分配。优势拥有绝对的寻址霸权根据下标查询的时间复杂度是绝对的O(1)。劣势增删元素时需要大面积挪动数据极其低效。链表Linked List内存中分散存储通过指针相连。优势增删元素只需改变指针指向时间复杂度O(1)。劣势查找数据只能从头节点顺藤摸瓜时间复杂度高达O(n)。痛点在高并发、大数据的业务场景中我们既想要数组的“秒查”又想要链表的“秒插”这能做到吗3.2 第一次进化哈希表数组 链表的诞生为了融合两者的优点哈希表Hash Table诞生了。它采用了拉链法Separate Chaining这也是 JDK7 及之前版本HashMap和ConcurrentHashMap的底层基石。结构解析主体是一个数组。当你插入一个 Key 时通过哈希函数计算出一个哈希值然后对数组长度取模定位到具体的数组下标也就是哈希桶Bucket。解决冲突如果两个不同的 Key 算出了相同的下标哈希冲突怎么办就把它们串成一个链表挂在这个数组节点下面。比喻数组就像是酒店的各个楼层哈希函数是电梯。你瞬间到达指定楼层O(1)如果这层住了多个人冲突你再挨个敲门寻找遍历链表。随着数据量激增或者遇到恶意攻击如 Hash DoS 攻击故意构造大量哈希值相同的 Key哈希表的脆弱一面暴露无遗。灾难再现当大量的元素全部堆积在同一个哈希桶时这个桶下面的链表会变得无限长。性能崩塌此时哈希表的查询效率从神坛跌落由O(1)极速退化成了单链表的O(n)。如果你去查一个在链表末尾的元素每一次查询都要经历漫长的遍历系统甚至会被直接拖垮。3.3 第二次终极进化JDK8红黑树的破局为了彻底封死链表过长导致的性能退化JDK8 对ConcurrentHashMap和HashMap的数据结构进行了史诗级大换血进化成了数组 链表 红黑树的终极形态。当发生哈希冲突时元素依然先追加到链表上但加入了严苛的“变异机制”树化触发Treeify当某一个哈希桶的链表长度达到 8并且整个数组的总容量达到 64时这条慵懒的单向链表就会瞬间变身为一棵结构严密的红黑树Red-Black Tree。退化触发Untreeify当进行扩容或删除节点操作后如果树中的节点数降到 6红黑树又会退化回普通链表利用 6 和 8 之间的差值防止在 7 这个临界点发生频繁的“树化-退化”抖动。为什么是红黑树红黑树是一种自平衡的二叉查找树。它的最大威力在于无论数据怎么插入它都能通过自身的左旋、右旋和变色保持树的相对平衡。这样一来即使发生最极端的哈希冲突在同一个桶里堆积了成千上万的数据红黑树也能将最坏情况下的查询时间复杂度死死钉在O(log n)例如100万条数据最多只需查询 20 次左右彻底杜绝了性能雪崩。并发控制视角的升华在 JDK8 的ConcurrentHashMap源码中当链表转为红黑树后桶的头节点会被替换为一个特殊的包装类TreeBin。此时如果其他线程来修改数据锁住的就是这个TreeBin节点。这不仅保证了读写的极高效率还在红黑树复杂的平衡调整左旋右旋过程中完美地维护了并发安全性。四、 常见面试问题五、 实战使用场景理解了底层原理我们来看看在实际编码中ConcurrentHashMap到底能干什么5.1 场景一本地高并发缓存 (Local Cache)在一些无需动用庞大 Redis 集群的轻量级场景下它可以作为服务本地的热点数据缓存抗住极高的并发读取。// 存储复杂的配置对象或数据字典避免高并发下反复查库 private static final ConcurrentHashMapString, ConfigObject configCache new ConcurrentHashMap(); public ConfigObject getConfig(String key) { // 利用 computeIfAbsent 保证高并发下即使缓存击穿也只有一个线程去执行数据库查询 return configCache.computeIfAbsent(key, k - loadConfigFromDatabase(k)); }5.2 场景二实时数据统计汇总非常适合用作多线程环境下的计数器例如统计 API 接口的实时访问次数、视频的实时弹幕数等。通常需要配合AtomicInteger或更加高效的LongAdder一起使用。private static final ConcurrentHashMapString, LongAdder apiRequestCounter new ConcurrentHashMap(); public void recordApiAccess(String apiName) { // 线程安全的复合操作如果 apiName 不存在则初始化一个 LongAdder随后进行原子的递增操作 apiRequestCounter.computeIfAbsent(apiName, k - new LongAdder()).increment(); }总结ConcurrentHashMap 的本质是 Java 在“线程安全”与“高并发性能”之间做出的工程化平衡。它并不像Hashtable那样对整个 Map 粗暴加锁而是通过CAS实现无锁化竞争volatile保证可见性与有序性synchronized只锁局部桶节点数组 链表 红黑树的复合结构优化查询效率从而实现高并发下的数据安全尽可能低的锁竞争极端哈希冲突下依然稳定的性能表现其底层设计思想本质上就是一句话能无锁就无锁必须加锁时就尽量缩小锁粒度。同时ConcurrentHashMap 也是 Java 并发编程思想的一个缩影它几乎串联起了JMMJava 内存模型可见性 / 原子性 / 有序性Happens-Before 原则synchronized / volatile / CAS红黑树CPU 缓存一致性等整个 Java 并发体系中的核心知识点。因此学习 ConcurrentHashMap不只是学习一个“线程安全容器”更是在真正理解 Java 高并发底层设计哲学。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2605430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…