嘿,大家好,我是小米,一个爱研究技术、也爱讲故事的 29 岁大哥哥。 上周我接了一个社招面试,面试官直接抛过来一个问题:“说说 Java 里的 HashMap 和 ConcurrentHashMap 有什么区别?”我心里一紧,心想这不是基础题嘛,结果我开口解释了半天,面试官的眼神却越来越微妙……嗯,这次面试翻车了。 所以,今天我决定好好梳理一下 HashMap 和 ConcurrentHashMap 的区别,希望我的教训能帮到大家! 开场故事:为什么需要 ConcurrentHashMap? 先想象一个场景:你和朋友们在餐厅点菜,每个人都可以随时往菜单里加菜。 问题来了:如果两个人同时修改菜单,服务员可能会拿到一份有问题的订单,比如一道菜被重复记录,或者有的菜根本没加上。 在单线程中,HashMap 这个"菜单"工作得很好。但到了多线程环境中,问题就来了:它本身不是线程安全的,多个线程同时操作会导致数据不一致。 于是,Java 提供了一个改进版的“菜单”——ConcurrentHashMap。不仅线程安全,还能保持一定的性能。 第一回合:结构上的对比 1. HashMap 的结构 HashMap 的底层是由数组和链表组成的,Java 8 以后为了提升性能,又在链表长度超过一定阈值时将链表转换为红黑树。 它的默认容量是 16,每次扩容时会翻倍到 32、64……以此类推。 2. ConcurrentHashMap 的结构 ConcurrentHashMap 的设计比 HashMap 复杂得多。Java 7 时,它使用了 Segment 作为分段锁的机制。Java 8 之后,Segment 被淘汰,改用了一种基于 CAS(Compare-And-Swap)操作和 Synchronized 锁的设计。 简单来说,ConcurrentHashMap 的核心在于 分段和细粒度锁,它的每个桶(bucket)可以独立加锁,从而提高并发性能。 第二回合:线程安全的实现 1. HashMap:线程不安全 HashMap 没有任何锁机制,完全是无锁设计。在多线程情况下,最典型的问题是死循环,例如两个线程同时触发扩容操作,导致循环链表形成,程序直接挂掉。 2. ConcurrentHashMap:线程安全 ConcurrentHashMap 使用了锁分段技术来实现线程安全: 读操作:在大多数情况下是无锁的,因为它使用了 volatile 修饰来保证可见性。 写操作:通过 CAS 操作和 Synchronized 来保证线程安全。 另外,它还有一个巧妙的设计:分段锁(Segmented Lock)。每个桶对应一个锁,多个线程可以同时操作不同的桶,避免了全表加锁的性能损耗。 第三回合:性能的对比 HashMap 的性能很高,因为它根本没有锁,单线程环境下表现优秀。但在多线程环境中会产生数据不一致的问题。 ConcurrentHashMap 引入了锁机制,多线程安全性大大提高,但性能上会稍逊于 HashMap。不过,Java 8 的优化让它在高并发环境中表现得非常高效。 第四回合:API 的使用差异 其实从使用层面来看,HashMap 和 ConcurrentHashMap 的 API 非常相似。 HashMap 的常用方法: put(K key, V value) get(Object key) remove(Object key) ConcurrentHashMap 的特有方法: putIfAbsent(K key, V value):只有当 key 不存在时才插入。 compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):对 key 对应的值进行重新计算。 merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction):如果 key 已存在,就用新的 value 与旧值进行合并。 这些增强版方法尤其适合在并发场景中使用。 面试总结:如果再遇到这个问题,我会怎么回答? “HashMap 和 ConcurrentHashMap 的区别可以从以下几个方面来看:” 1、线程安全性: HashMap 是线程不安全的,在多线程环境中不能直接使用。 ConcurrentHashMap 是线程安全的,通过分段锁和 CAS 实现高效并发。 2、底层结构: HashMap 使用数组+链表/红黑树。 ConcurrentHashMap 使用分段结构,每个桶独立加锁。 3、并发性能: HashMap 在单线程环境中性能最佳。 ConcurrentHashMap 在多线程环境中表现优异,尤其适合高并发场景。 4、API 支持: ConcurrentHashMap 增强了线程安全的 API,如 putIfAbsent、compute 和 merge。 结尾感悟:面试翻车也是成长的机会 虽然那次面试没通过,但这个问题让我有了更深的认识,也提醒我:技术上的基础知识不能掉以轻心。 希望这篇文章能帮助到你们,如果你也有类似的面试翻车经历,欢迎留言分享,让我们一起成长吧! 记得点赞、关注,小米陪你一路技术进阶! 我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
一次“面试翻车”后的思考:HashMap和ConcurrentHashMap的区别到底在哪里?
软件求生
2025-01-05 21:22:17
0
阅读:9