ConcurrentHashMap与SynchronizedMap对比解析
偷偷努力,悄无声息地变强,然后惊艳所有人!哈哈,小伙伴们又来学习啦~今天我将给大家介绍《Java并发集合对比:ConcurrentHashMap vs SynchronizedMap》,这篇文章主要会讲到等等知识点,不知道大家对其都有多少了解,下面我们就一起来看一吧!当然,非常希望大家能多多评论,给出合理的建议,我们一起学习,一起进步!
ConcurrentHashMap性能优于Collections.synchronizedMap,因其采用CAS与synchronized结合的细粒度锁机制,支持高并发读写;而synchronizedMap使用全局锁,导致高并发下线程阻塞严重。前者在JDK 8中以桶为单位加锁,读操作无锁,写操作仅锁定冲突桶,并支持链表转红黑树优化性能;后者所有方法均同步,吞吐量低。此外,ConcurrentHashMap不支持null键值,提供原子复合操作如putIfAbsent,迭代器弱一致性;synchronizedMap允许null键值,迭代器快速失败,复合操作需外部同步。低并发或需快速失败迭代器时可选synchronizedMap,但多数场景ConcurrentHashMap更优。

在Java并发编程的语境下,ConcurrentHashMap 和 Collections.synchronizedMap 都是为了解决多线程环境下 HashMap 的非线程安全问题而生。但要论“终极性能”,我个人会毫不犹豫地把票投给 ConcurrentHashMap。它在设计之初就考虑了高并发场景下的性能和可伸缩性,而 synchronizedMap 更多的是一种“打补丁”式的解决方案,用全局锁来简单粗暴地实现线程安全,这在高并发下往往会成为性能瓶颈。
解决方案
要深入理解两者的性能差异,我们得从它们实现线程安全的底层机制说起。Collections.synchronizedMap(new HashMap<>()) 的工作原理非常直接:它返回一个 Map 的包装器,这个包装器中的所有公共方法(包括 get、put、remove、size 等)都被 synchronized 关键字修饰,锁住的是这个包装器对象本身。这意味着在任何给定时刻,只有一个线程能够访问这个 Map 的任何一个操作。想象一下,你有一个巨大的图书馆,但只有一个入口,所有读者(线程)无论是借书、还书还是查阅,都必须排队通过这唯一的入口。在高并发场景下,这种全局锁机制会导致严重的竞争,大量线程会因为等待锁而被阻塞,从而极大地降低吞吐量和性能。
而 ConcurrentHashMap 的设计哲学则完全不同。它采取了一种更精细、更智能的并发控制策略。在JDK 7及之前,ConcurrentHashMap 采用了分段锁(Segment Locking)的机制,将整个 HashMap 分割成若干个段(Segment),每个段都是一个独立的 HashEntry 数组,并拥有自己的锁。这样,不同的线程就可以同时访问不同的段,进行读写操作,大大减少了锁的粒度。就好比图书馆里有多个独立的阅览室,每个阅览室都有自己的门禁,读者可以同时进入不同的阅览室。
到了JDK 8,ConcurrentHashMap 的实现进一步优化,彻底放弃了分段锁,转而采用了一种更加细粒度的并发控制:结合了CAS(Compare-And-Swap)操作和 synchronized 关键字。它将 HashMap 的桶(bin)作为基本的锁单元。对于非冲突的操作(比如对不同桶的写入),ConcurrentHashMap 可以通过CAS操作实现无锁化;而当出现哈希冲突或需要修改特定桶时,它会只对那个桶的头节点进行 synchronized 锁定。更妙的是,在大多数读操作中,ConcurrentHashMap 甚至不需要加锁,因为它利用了 volatile 关键字和内存屏障来保证数据可见性。这种设计允许大量的并发读操作几乎不受阻塞,并且不同桶之间的写操作也能并行进行,从而在高并发环境下展现出卓越的性能和可伸缩性。
所以,从根本上讲,synchronizedMap 是悲观锁的典型代表,锁的粒度大;而 ConcurrentHashMap 则是乐观锁(CAS)与悲观锁(synchronized)的结合,并且锁的粒度极小,甚至在很多情况下避免了锁。这直接决定了 ConcurrentHashMap 在处理大量并发请求时,能够提供远超 synchronizedMap 的吞吐量和响应速度。
ConcurrentHashMap 到底是如何实现高性能并发的?
要理解 ConcurrentHashMap 的高性能,我们得稍微深入它的内部构造。在JDK 8中,ConcurrentHashMap 的核心思想是减少锁的竞争,并尽可能地允许并发操作。
首先,它内部的哈希表结构由 Node 组成,每个 Node 代表一个键值对。当多个线程尝试修改不同的桶时,它们通常不会相互阻塞。这是因为 ConcurrentHashMap 对不同的桶(即 table 数组的不同索引位置)使用独立的锁。具体来说,当一个线程需要修改某个桶中的数据时,它会尝试对该桶的头节点进行 synchronized 锁定。这意味着,如果两个线程修改的是不同的桶,它们可以并行执行,互不影响。
其次,对于读操作,ConcurrentHashMap 几乎是无锁的。它利用 volatile 关键字保证了 Node 数组的可见性,使得一个线程写入的数据,其他线程能够立即看到。在 get() 方法中,它只是简单地遍历哈希桶,查找对应的键,这个过程不需要任何锁。这种设计极大地提升了读操作的性能,因为读操作是并发集合中最频繁的操作之一。
此外,ConcurrentHashMap 还巧妙地结合了CAS操作来处理一些非冲突的更新。例如,当一个桶为空,第一次插入元素时,它会尝试使用CAS操作来设置 Node。只有当CAS失败(意味着其他线程抢先一步插入了),它才会退回到使用 synchronized 锁来保证原子性。
最后,当哈希冲突严重导致某个桶的链表过长时(默认阈值是8),ConcurrentHashMap 会将这个链表转换成红黑树,以保证查找、插入和删除操作的最坏时间复杂度为 O(log n),而不是 O(n)。在对红黑树进行操作时,同样会使用 synchronized 锁来保证线程安全。
这种多层次、混合式的并发控制策略,使得 ConcurrentHashMap 在绝大多数并发场景下都能表现出卓越的性能。它不是简单地给所有操作加锁,而是根据操作类型和冲突程度,智能地选择最合适的并发控制手段,从而实现了高并发下的高吞吐量和低延迟。
什么时候我们仍然应该考虑使用 SynchronizedMap?
尽管 ConcurrentHashMap 在性能上碾压 synchronizedMap,但在某些特定场景下,synchronizedMap 依然有其存在的价值,或者说,使用它并不会带来明显的劣势,甚至可能更简单直观。
在我看来,最主要的情况是低并发环境。如果你的应用程序中,对 Map 的并发访问非常少,或者说并发线程数极低,那么 synchronizedMap 的全局锁开销可能根本不会成为性能瓶颈。在这种情况下,ConcurrentHashMap 内部更复杂的机制(比如更多的内存开销、CAS操作的循环重试等)反而可能带来微小的额外开销。虽然这个开销通常可以忽略不计,但如果你的首要目标是代码的简洁性和易理解性,synchronizedMap 可能会显得更直接。
其次,当外部已经存在严格的同步机制时。比如,你可能在一个大的 synchronized 块内部操作一个 Map,或者你的整个业务逻辑本身就已经是单线程处理的,只是偶尔会被其他线程访问。在这种情况下,synchronizedMap 提供的那层额外同步可能显得多余,但也不会造成伤害,因为它只是提供了一层“保障”。
再者,如果你的业务逻辑对迭代器的一致性有非常严格的要求,并且你能够确保在迭代期间外部不会修改 Map。synchronizedMap 的迭代器是快速失败(fail-fast)的,这意味着如果在迭代过程中 Map 被其他线程修改了,它会立即抛出 ConcurrentModificationException。这在某些场景下可以帮助你快速发现并发问题。而 ConcurrentHashMap 的迭代器是弱一致性(weakly consistent)的,它可能不会反映迭代器创建之后的所有修改,但也不会抛出异常。这两种行为模式各有优劣,取决于你的具体需求。
最后,遗留系统和兼容性也是一个考虑因素。在一些老旧的项目中,可能已经大量使用了 synchronizedMap,如果性能不是瓶颈,并且重构到 ConcurrentHashMap 会带来较大的风险和工作量,那么保持现状也未尝不可。毕竟,工程师的时间和项目的稳定性同样重要。
总的来说,选择 synchronizedMap 更多是出于简单性、低并发场景下的足够性以及特定迭代器行为的考量。一旦你预见到有中高并发的可能,或者对性能有哪怕一点点要求,ConcurrentHashMap 几乎总是更优的选择。
除了性能,两者在功能和行为上还有哪些关键差异?
除了性能上的巨大鸿沟,ConcurrentHashMap 和 synchronizedMap 在功能和行为上还存在几个关键且容易被忽视的差异,这些差异有时会影响你的编程决策。
第一个显著区别是对 null 键和 null 值的支持。ConcurrentHashMap 不允许 null 键或 null 值。如果你尝试插入 null 键或 null 值,它会抛出 NullPointerException。这是出于设计上的考量,null 在并发环境中可能导致歧义和复杂性,比如无法区分一个键是不存在还是其值就是 null。而 synchronizedMap 因为内部包装的是 HashMap,所以它允许一个 null 键和多个 null 值,这与 HashMap 的行为保持一致。这个差异在使用时需要特别注意,尤其是在从非并发代码迁移到并发代码时。
第二个是复合操作的原子性。synchronizedMap 的所有单个操作(如 put、get、remove)都是原子性的,因为它们都通过全局锁保护。但如果你的操作涉及到多个步骤,例如 if (!map.containsKey(key)) map.put(key, value); 这样的“先检查后执行”的复合操作,synchronizedMap 并不能保证整个复合操作的原子性。在 containsKey 和 put 之间,其他线程仍然可能修改 Map。要保证复合操作的原子性,你仍然需要外部的 synchronized 块来包裹整个逻辑。ConcurrentHashMap 同样如此,它的单个操作是线程安全的,但复合操作也需要额外的同步措施。不过,ConcurrentHashMap 提供了一些原子性的复合操作方法,比如 putIfAbsent()、compute()、merge() 等,这些方法可以在内部以原子方式执行,从而简化了部分复合操作的实现。
第三个是迭代器的一致性模型。前面提到过,synchronizedMap 的迭代器是快速失败(fail-fast)的。这意味着如果在迭代过程中,除了迭代器自身的 remove() 方法之外,Map 被任何其他方式(包括其他线程)结构性地修改了,迭代器会立即抛出 ConcurrentModificationException。这有助于在开发阶段发现并发修改的问题。而 ConcurrentHashMap 的迭代器是弱一致性(weakly consistent)的。它会反映迭代器创建时 Map 的状态,但可能不会反映迭代器创建之后的所有修改,也不会抛出 ConcurrentModificationException。这意味着你可能会看到部分更新,或者错过一些更新,但程序不会崩溃。这种设计是为了在并发环境中提供更高的可用性和吞吐量,但代价是迭代结果的严格一致性。
理解这些非性能层面的差异,对于在特定场景下做出正确的选择至关重要。你不仅仅要考虑“快不快”,还要考虑“好不好用”、“会不会出问题”以及“我的数据模型是否允许 null”。
本篇关于《ConcurrentHashMap与SynchronizedMap对比解析》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于文章的相关知识,请关注golang学习网公众号!
“贤弟”是古人对同辈的尊称。
- 上一篇
- “贤弟”是古人对同辈的尊称。
- 下一篇
- JavaScript闭包与作用域链解析
-
- 文章 · java教程 | 5小时前 |
- Java集合高效存储技巧分享
- 164浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- JavaOpenAPI字段命名配置全攻略
- 341浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java接口定义与实现全解析
- 125浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java对象与线程内存交互全解析
- 427浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- JPA枚举过滤技巧与实践方法
- 152浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java获取线程名称和ID的技巧
- 129浏览 收藏
-
- 文章 · java教程 | 7小时前 |
- JavanCopies生成重复集合技巧
- 334浏览 收藏
-
- 文章 · java教程 | 7小时前 |
- Windows配置Gradle环境变量方法
- 431浏览 收藏
-
- 文章 · java教程 | 7小时前 |
- Java合并两个Map的高效技巧分享
- 294浏览 收藏
-
- 文章 · java教程 | 7小时前 | java class属性 Class实例 getClass() Class.forName()
- Java获取Class对象的4种方式
- 292浏览 收藏
-
- 文章 · java教程 | 7小时前 |
- Java正则表达式:字符串匹配与替换技巧
- 183浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java处理外部接口异常的正确方法
- 288浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3182次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3393次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3424次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4528次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3802次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

