Java 八股文背熟了,为什么一追问就卡壳

· · 9 分钟阅读

Java 八股文能背熟,面试却常常卡在第二、第三个问题上:答完“HashMap 怎么扩容”,面试官接着问“那多线程下呢”,答案就散了。追问考的不是你背了多少,而是各个知识点之间有没有连上。这篇文章按“一个起点问题 + 一条追问链”的方式,梳理集合、并发、JVM 三块里最常被追问的内容,每个结论都对照了 Oracle 官方文档、Java 语言规范或 OpenJDK 源码,并标明了哪些是文档写的、哪些是源码里的实现细节。

为什么背完了还会被追问问住

“八股文”指的是被反复考到、答案相对固定的基础题。面试官用它们起头,是因为答案标准、容易判断。但追问往往朝三个方向走:换个条件(单线程换成多线程)、问为什么(为什么容量是 2 的幂)、问边界(什么情况下这个结论不成立)。

只背结论的答案,缺的正是“条件”和“边界”。所以下面每一节都按同一个结构写:起点问题、核心答案、三个追问、容易说错的点。你可以拿它当自测清单,对着每一条追问自己讲一遍。

一、HashMap:从“怎么扩容”追问到“线程安全”

起点:HashMap 的扩容是怎么回事?

Javadoc 写明:默认初始容量 16、默认负载因子 0.75;当条目数超过“负载因子 × 当前容量”时,哈希表会被 rehash,桶的数量大约变为原来的两倍。

追问 1:负载因子为什么是 0.75?

官方的说法很克制:“as a general rule, the default load factor (.75) offers a good tradeoff between time and space costs”,即它是时间与空间成本之间的折中。不要编造“数学推导”,说清“折中”和“预估容量可以避免 rehash”就够了。Javadoc 也提醒:初始容量大于“最大条目数 ÷ 负载因子”时,不会发生 rehash。

追问 2:链表什么时候变成红黑树?

这一点Javadoc 没有写,是 OpenJDK 源码里的实现细节:TREEIFY_THRESHOLD = 8、UNTREEIFY_THRESHOLD = 6、MIN_TREEIFY_CAPACITY = 64(我们在 OpenJDK 主干的 HashMap.java 中核对过)。回答时可以说“源码里链表长度到 8 会考虑转树,但表容量不足 64 时通常优先扩容”,并说明这是实现细节,不同版本可能调整。Javadoc 里与之相关的一句是:键实现 Comparable 时,可能用比较顺序来打破 hashCode 相同的僵局。

追问 3:多线程下用 HashMap 会怎样?

官方答案很明确:HashMap 不是同步的,多个线程并发访问且至少一个线程做了结构性修改时,必须在外部加同步。“会死循环”这类具体现象与 JDK 版本有关,面试里不必展开,答“行为不可依赖,要用 ConcurrentHashMap 或外部同步”最稳。

容易说错:HashMap 允许一个 null 键和 null 值,ConcurrentHashMap 都不允许;迭代器是 fail-fast 的,但 Javadoc 明说这只是 best-effort,只用来发现 bug,不能当作程序正确性的依据。

二、ConcurrentHashMap:别只会说“分段锁”

起点:ConcurrentHashMap 怎么保证线程安全?

较老的版本(JDK 7 及更早)按分段(Segment)加锁。现在的 Javadoc 只保留了 concurrencyLevel 作为“兼容旧版本的大小提示”。在 OpenJDK 当前源码里能看到:往空桶放入首个节点用 CAS(casTabAt),桶里已有节点时对该桶的头节点加 synchronized。面试里最好说成“JDK 8 之后按桶粒度:空桶 CAS,非空桶锁头节点”,而不是还在讲分段锁。

追问 1:get 要不要加锁?

Javadoc:检索操作(包括 get)通常不阻塞,可以和 put、remove 重叠。同时它写明,对同一个键的更新操作与返回该更新值的检索之间存在 happens-before 关系,所以读到的值是安全发布的。

追问 2:size() 准不准?

Javadoc:在并发更新时,size、isEmpty、containsValue 这类聚合方法反映的是瞬时状态,适合监控和估算,不适合用来控制程序逻辑;mappingCount() 返回的也是估计值。

追问 3:先 get 再 put 线程安全吗?

不安全,两个操作之间别的线程可以插进来。要用原子的复合方法如 computeIfAbsent、merge。但 Javadoc 也提醒:计算过程中其他线程对该映射的某些更新可能被阻塞,所以计算要短而简单。

容易说错:迭代器是弱一致的,不会抛 ConcurrentModificationException,但只设计给单个线程使用。

三、volatile 与 synchronized:把“可见性”讲到 happens-before

起点:volatile 有什么用?

Java 语言规范 §17.4.5 写明:对 volatile 字段的写 happens-before 之后对该字段的每一次读。所以 volatile 解决的是可见性和有序性。

追问 1:volatile 能保证 i++ 线程安全吗?

不能。i++ 是读、改、写三步,volatile 只保证每一步的可见性,不保证三步合在一起不被打断。要用 AtomicInteger 或加锁。这是最常见的追问。

追问 2:synchronized 和 volatile 的 happens-before 有什么相同点?

规范同样写明:对监视器的解锁 happens-before 之后对同一监视器的每次加锁。此外还有两条常被忽略:线程的 start() 调用 happens-before 被启动线程里的任何动作;一个线程里的所有动作 happens-before 其他线程从它的 join() 成功返回。

追问 3:long 和 double 的读写是原子的吗?

规范 §17.7:对非 volatile 的 long 或 double 的单次写,在内存模型里被视为两次独立的写(每个 32 位一半);而 volatile 的 long 和 double 读写始终是原子的。

容易说错:把 happens-before 说成“时间上先发生”。规范里它是一种偏序关系,保证的是可见性与顺序约束,而不是“先执行完”。

四、JVM:GC 与线程模型的几个“当前版本”问题

起点:现在默认用哪个垃圾收集器?

Oracle 的 JDK 21 垃圾收集调优指南写明:“G1 is the default collector.”G1 把堆划分为一组大小相同的 region,region 是内存分配和回收的单位。

追问 1:Mixed 收集是什么?

指南写明:空间回收阶段由多次 young 收集组成,这些收集除了年轻代 region,还会疏散若干老年代 region 里的存活对象,这类收集称为 Mixed collections。

追问 2:G1 的停顿时间目标能保证吗?

文档的说法是“尝试以高概率达到停顿目标”,不是保证。回答时不要说成“一定不超过 X 毫秒”。具体数值请用自己项目的日志和压测说话。

追问 3:虚拟线程和线程池是什么关系?

JDK 21 文档:虚拟线程是 java.lang.Thread 的实例,但不绑定到特定的操作系统线程;并明确说虚拟线程不稀缺,所以“never be pooled”。在 synchronized 代码块或方法里运行时,虚拟线程可能被 pin 在载体线程上,文档说这不会让程序出错,但可能影响可伸缩性。这一点随 JDK 版本有变化,回答时说明你参照的版本。

五、怎么练“追问”:一个可重复的方法

  1. 每道题写出三层:一句话结论;一个条件变化后的结论;一个边界反例。
  2. 分清来源:文档写明的、规范规定的、源码里的实现细节、版本相关的。面试里说“这是实现细节,不同版本可能变”,比硬说“一定是”更可信。
  3. 用自己的话讲给别人听,或录下来回放,找出你开始含糊的那一步,那就是追问会击穿的地方。
  4. 补一个你真写过的例子:线上用 ConcurrentHashMap 遇到过什么坑。没有就如实说没有,不要编项目。

想知道面试官手撕代码时看什么,可以读《手撕代码面试:面试官到底在看什么》;备考时间怎么分配见《技术面试怎么准备》;刷题与真实面试的落差见《刷了四百道力扣,为什么面试还是挂》。

如果面试里被追问到答不上来

先承认边界,再给推理路径:“这一点我不确定源码细节,但按 Javadoc 的说明,结论应该是……”,比沉默或编造都好。

Interview Copilot 是桌面端的实时面试提示工具:监听通话的系统声音,识别问题并给出回答提示,也能对屏幕上的代码题截图给出思路,有无需绑卡的免费方案。使用前请先问面试方是否允许使用辅助工具,不允许就不要用;在 macOS 和 Windows 上它的窗口会请求不被屏幕捕获,在 Linux 上窗口可见,具体效果取决于你的共享软件,请自己做私下测试。相关页面:AI 编程面试助手、免费方案。辅助工具不能替代你对知识点的理解,上面的追问链还是要自己练。

参考来源

以上页面于 2026 年 11 月 12 日查阅。源码为主干版本,细节可能随 JDK 版本变化。

FAQ

Java八股文是什么意思?

指面试里被反复考到、答案相对固定的基础知识题,如集合、并发、JVM 等。它适合用来起头,但面试官常用追问检验你是否真正理解,只背结论容易在第二个问题上卡住。

HashMap 的负载因子为什么是 0.75?

Oracle 的 Javadoc 只说默认负载因子 0.75 是时间与空间成本之间的折中,没有给出推导。回答时说清折中,再补一句:预估好初始容量可以避免 rehash。

HashMap 链表什么时候转红黑树?

这是实现细节,Javadoc 没写。OpenJDK 源码里 TREEIFY_THRESHOLD 为 8、UNTREEIFY_THRESHOLD 为 6、MIN_TREEIFY_CAPACITY 为 64。回答时说明这是源码常量,不同版本可能变化。

volatile 能保证线程安全吗?

不能保证复合操作。Java 语言规范规定 volatile 写 happens-before 后续的读,解决的是可见性和有序性。i++ 是读改写三步,需要 AtomicInteger 或加锁。

ConcurrentHashMap 的 size() 准确吗?

并发更新时不适合用来控制逻辑。Javadoc 说 size、isEmpty 等聚合方法反映的是瞬时状态,适合监控与估算;mappingCount() 返回的也是估计值。

面试被追问到答不上来怎么办?

先承认不确定的部分,再说明你依据的文档或推理路径,比如“按 Javadoc 的说明应当是……”。不要编造细节,也不要编造项目经历。面试前可向招聘方确认是否允许辅助工具。

应用在这里如何帮忙

相关文章

继续阅读