0

0

Java并发集合终极性能对比:ConcurrentHashMap vs SynchronizedMap

狼影

狼影

发布时间:2025-09-03 19:21:01

|

255人浏览过

|

来源于php中文网

原创

ConcurrentHashMap性能优于Collections.synchronizedMap,因其采用CAS与synchronized结合的细粒度锁机制,支持高并发读写;而synchronizedMap使用全局锁,导致高并发下线程阻塞严重。前者在JDK 8中以桶为单位加锁,读操作无锁,写操作仅锁定冲突桶,并支持链表转红黑树优化性能;后者所有方法均同步,吞吐量低。此外,ConcurrentHashMap不支持null键值,提供原子复合操作如putIfAbsent,迭代器弱一致性;synchronizedMap允许null键值,迭代器快速失败,复合操作需外部同步。低并发或需快速失败迭代器时可选synchronizedMap,但多数场景ConcurrentHashMap更优。

java并发集合终极性能对比:concurrenthashmap vs synchronizedmap

在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
关键字和内存屏障来保证数据可见性。这种设计允许大量的并发读操作几乎不受阻塞,并且不同桶之间的写操作也能并行进行,从而在高并发环境下展现出卓越的性能和可伸缩性。

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

所以,从根本上讲,

synchronizedMap
是悲观锁的典型代表,锁的粒度大;而
ConcurrentHashMap
则是乐观锁(CAS)与悲观锁(synchronized)的结合,并且锁的粒度极小,甚至在很多情况下避免了锁。这直接决定了
ConcurrentHashMap
在处理大量并发请求时,能够提供远超
synchronizedMap
的吞吐量和响应速度。

ConcurrentHashMap 到底是如何实现高性能并发的?

要理解

ConcurrentHashMap
的高性能,我们得稍微深入它的内部构造。在JDK 8中,
ConcurrentHashMap
的核心思想是减少锁的竞争,并尽可能地允许并发操作

首先,它内部的哈希表结构由

Node<K,V>[] table
组成,每个
Node
代表一个键值对。当多个线程尝试修改不同的桶时,它们通常不会相互阻塞。这是因为
ConcurrentHashMap
对不同的桶(即
table
数组的不同索引位置)使用独立的锁。具体来说,当一个线程需要修改某个桶中的数据时,它会尝试对该桶的头节点进行
synchronized
锁定。这意味着,如果两个线程修改的是不同的桶,它们可以并行执行,互不影响。

其次,对于读操作

ConcurrentHashMap
几乎是无锁的。它利用
volatile
关键字保证了
Node
数组的可见性,使得一个线程写入的数据,其他线程能够立即看到。在
get()
方法中,它只是简单地遍历哈希桶,查找对应的键,这个过程不需要任何锁。这种设计极大地提升了读操作的性能,因为读操作是并发集合中最频繁的操作之一。

此外,

ConcurrentHashMap
还巧妙地结合了CAS操作来处理一些非冲突的更新。例如,当一个桶为空,第一次插入元素时,它会尝试使用CAS操作来设置
Node
。只有当CAS失败(意味着其他线程抢先一步插入了),它才会退回到使用
synchronized
锁来保证原子性。

最后,当哈希冲突严重导致某个桶的链表过长时(默认阈值是8),

ConcurrentHashMap
会将这个链表转换成红黑树,以保证查找、插入和删除操作的最坏时间复杂度为
O(log n)
,而不是
O(n)
。在对红黑树进行操作时,同样会使用
synchronized
锁来保证线程安全。

这种多层次、混合式的并发控制策略,使得

ConcurrentHashMap
在绝大多数并发场景下都能表现出卓越的性能。它不是简单地给所有操作加锁,而是根据操作类型和冲突程度,智能地选择最合适的并发控制手段,从而实现了高并发下的高吞吐量和低延迟。

Bolt.new
Bolt.new

Bolt.new是一个免费的AI全栈开发工具

下载

什么时候我们仍然应该考虑使用 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
”。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

847

2023.08.22

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

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

75

2025.10.23

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

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

765

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

377

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

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

32

2026.01.21

C++多线程相关合集
C++多线程相关合集

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

29

2026.01.21

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

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

76

2026.03.11

热门下载

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

精品课程

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

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 81.1万人学习

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

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