Java 后端面试里,HashMap 和 ConcurrentHashMap 常常被连续追问。很多候选人能说出数组、链表、红黑树,也知道 ConcurrentHashMap 是线程安全的,但一到“为什么扩容危险”“并发扩容怎么做”“用了并发容器是不是就不用考虑锁”,就容易回答得很散。
这类题真正考察的不是你记住了几个源码细节,而是你是否理解共享状态的风险。后端系统里大量问题都和共享状态有关:本地缓存、限流计数、任务状态、连接管理、会话数据。集合只是入口,面试官借它观察你的并发意识。
先把 HashMap 的风险讲清楚
HashMap 在单线程下很好用,但并发写入会有数据覆盖、结构不一致、扩容期间状态异常等风险。不要把重点放在“某个版本会不会死循环”这种单点记忆上,更稳的表达是:它没有为并发修改提供一致性保护,多个线程同时写入、扩容或修改链表结构时,内部状态可能被破坏。
这句话背后要有工程意识:只读不代表永远安全,如果有线程在修改,其他线程读到的状态也可能不是你预期的。面试里如果被问本地缓存能不能用 HashMap,答案应该先问是否有并发写、是否需要过期、是否需要容量控制,而不是直接说可以或不可以。
ConcurrentHashMap 解决的是结构安全
ConcurrentHashMap 的价值,是在并发访问下提供更安全的结构修改和更好的并发性能。它通过更细粒度的同步、CAS、桶级别控制等方式,降低全表加锁带来的性能损耗。不同 JDK 版本实现不同,面试时可以不用死背每行源码,但要讲出核心目标:减少锁冲突,同时保证内部结构不会被并发写破坏。
不过,它不是业务一致性的万能答案。比如“如果 key 不存在就初始化一个资源”,你需要用原子方法,不能先 containsKey 再 put;比如“扣减库存”不能因为用了 ConcurrentHashMap 就认为业务安全,因为库存一致性往往要由数据库、分布式锁或条件更新兜底。
四个追问可以串成一条线
第一个追问通常是“它是不是绝对线程安全”。更成熟的回答是:单个方法调用可以保证并发结构安全,但组合操作仍要看是否原子。
第二个追问是“为什么不用 Hashtable”。这里不要只说旧,而要说全表锁并发度低,现代高并发场景更关注锁粒度和冲突范围。
第三个追问是“size 一定准确吗”。这个问题考的是并发统计成本:在写入不断发生时,想拿到完全一致的全局数量并不免费,要看实现策略和业务是否真的需要强一致数量。
第四个追问是“putIfAbsent 有什么意义”。它的价值不是少写一行代码,而是把检查和写入合成一个原子动作,避免两个线程同时初始化同一个 key。
项目里要讲共享状态边界
如果简历里写了本地缓存、任务调度、限流器,都可能被追问集合并发。一个成熟回答可以是:我不会只因为用了并发容器就放松业务校验。并发容器负责结构安全,业务层还要保证操作原子性、过期策略、容量上限和异常恢复。
举个例子,本地缓存如果无限增长,会变成内存问题;如果多个线程同时初始化同一个 key,要避免重复加载;如果缓存值本身是可变对象,还要考虑对象内部状态是否会被多个线程同时修改。这样回答比单纯背 ConcurrentHashMap 源码更接近后端真实问题。
准备这类题时,可以按三层组织:第一层,HashMap 在并发下为什么不安全;第二层,ConcurrentHashMap 用什么思路降低冲突;第三层,并发容器在项目里仍然有哪些业务边界。能讲到第三层,面试官通常会认为你不只是会背 Java 集合,而是理解并发编程的代价。