“我被问懵了!这两者不都是线程安全的吗?但面试官的微笑让我不安……”
嗨,大家好,我是小米,一个热爱分享技术的老码农(虽然不老,31岁正当年!)。今天我们来聊聊 Java 面试中一道高频问题:SynchronizedMap 和 ConcurrentHashMap 有什么区别?
这道题看似简单,但其实暗藏玄机,一不小心就容易掉坑。今天我们就通过一个故事 + 源码剖析 + 性能对比,彻底搞清楚它们的区别。
故事开场:面试现场的灵魂拷问某天,小王(5年Java开发经验)去参加阿里社招面试。
面试官看着简历,笑了笑:“你写过高并发项目?”
小王点头:“是的,我们系统日均 QPS 过百万,主要用 HashMap 处理缓存。”
面试官挑了挑眉:“哦?那你能说说 SynchronizedMap 和 ConcurrentHashMap 的区别吗?”
小王:“呃……它们都能保证线程安全……”
面试官:“嗯?那如果并发度很高,你会选哪个?”
小王开始紧张了:“这个……它们……嗯……”
面试官一笑:“好吧,我们换个问题……”
小王当场泪目,回去后痛定思痛,决定彻底搞懂这两个玩意儿!
那么,它们到底有什么区别呢?
SynchronizedMap vs ConcurrentHashMap 的核心区别我们先来一个总结表格,直观感受一下两者的不同:
看完表格,我们一个个拆解讲解。
SynchronizedMap:简单粗暴的“全局锁”首先,SynchronizedMap 其实是 Collections.synchronizedMap() 生成的,它的本质是对 HashMap 进行“全局加锁”,保证每次访问都只能有一个线程进入。
我们看看源码(简化版):
内部实现其实就是给 所有方法都加上 synchronized,让所有操作串行化:
这样虽然保证了线程安全,但也导致每次操作都要获取整个 Map 的锁,性能相当低,在高并发环境下会变成性能瓶颈!
适用场景
适用于并发较低,或者读多写少的场景,比如:
配置缓存
线程数不多的 Web 应用
需要简单线程安全的地方(例如同步读取一小块数据)
如果是高并发环境,那就该换 ConcurrentHashMap 了!
ConcurrentHashMap:高并发神器ConcurrentHashMap 是 Java 5 引入的,主要是为了提高并发性能。它的核心优化点有两个:
分段锁(Segment)(JDK 1.7 之前)
CAS + 自旋锁(JDK 1.8 之后)
让我们看看 JDK 1.8 版本的 ConcurrentHashMap 怎么做到高性能的。
(1)底层结构
在 JDK 1.8 之前,ConcurrentHashMap 采用 分段锁(Segment),但 JDK 1.8 之后直接用 数组 + 链表 + 红黑树 代替了分段锁,提高了效率。
存储结构:类似 HashMap,但每个桶(bucket)都是 链表 + 红黑树,高并发时可变成红黑树加速查询。
锁机制:不锁整个 Map,而是只锁住某个 bucket,这样多个线程可以同时访问不同的 bucket,提高并发能力。
CAS(Compare And Swap):无锁操作,提升性能。
(2)put() 方法解析
看源码(简化版):
可以看到,它不会锁整个 Map,而是:
先用 CAS 尝试放入数据,避免不必要的锁竞争。
如果 CAS 失败,再只锁住当前 bucket,而不是整个 Map,提高了并发性能。
适用场景
高并发环境(推荐)
大数据量存储
读写并重的场景
缓存(如 LRU Cache)
性能对比实验我们做个小实验,对比两者的性能(JMH 基准测试):
实验结果(吞吐量 ops/sec):
可以看到,并发越高,SynchronizedMap 退化得越厉害,而 ConcurrentHashMap 能保持高性能。
总结:面试高分回答如果你在面试中遇到这个问题,你可以这样答:
“SynchronizedMap 是对整个 HashMap 加锁,适用于低并发场景。而 ConcurrentHashMap 通过 分段锁 + CAS 提高并发性能,在高并发环境下比 SynchronizedMap 更优。并且,SynchronizedMap 允许 null 键值,而 ConcurrentHashMap 不允许。”
这下,面试官该对你点头了吧?
END今天的分享到此结束,关注我,带你学更多 Java 高级知识!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!