JJava 知识库
JAVA INTERVIEW

高频面试题

Java多线程进阶约 3 分钟

一段并发代码单测一直正常,上线后偶尔读到旧值,你会如何从 Java 内存模型解释?

参考回答约 3 分钟 · 口语表达
先说结论

先说结论:Java 内存模型规定线程如何通过内存交互,以及哪些执行结果是合法的。happens-before 是可见性与顺序保证:如果 A happens-before B,则 A 的结果对 B 可见,且 A 在逻辑顺序上先于 B。

01

我先给结论,再说明它在项目里解决什么问题。从原子性、可见性、有序性和安全发布理解并发正确性。Java 内存模型规定线程如何通过内存交互,以及哪些执行结果是合法的。happens-before 是可见性与顺序保证:如果 A happens-before B,则 A 的结果对 B 可见,且 A 在逻辑顺序上先于 B。 常见规则包括程序次序、监视器解锁先于后续加锁、volatile 写先于后续读、线程 start 前操作先于新线程、线程内操作先于其他线程成功 join 返回,以及传递性。

02

核心机制我会按一次真实执行过程来讲。沿着「源代码操作 → 编译器与 CPU 重排序 → 线程本地执行与缓存 → 同步动作建立 happens-before → 其他线程获得合法可见结果」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 源代码操作 JMM 定义程序动作与线程间可观察结果,屏蔽不同 CPU 内存模型差异。 编译器与 CPU 重排序 只要不改变单线程语义,编译器和处理器可以重排序;数据竞争程序不能用直觉推断执行顺序。

03

实现细节只抓关键入口,不会整段背源码。JLS 17.4:内存动作、同步顺序与 happens-before。 HotSpot OrderAccess/VarHandle:屏障在 JVM 的实现入口。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。

04

放到生产使用时,我会关注参数和验证数据。用 jcstress 验证安全发布、this 逸出和消息传递;不要用 sleep 证明正确,必须枚举允许/禁止结果。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「forbidden outcome」为主基线,记录值应满足「必须 0」;同时保存 数据竞争检测结果、旧值或默认值样本,使后续变化能够回到同一时间轴比较。

05

最后补充常见误区和使用边界。instance 引用未声明 volatile 时,对象分配、构造和引用赋值可能被重排序。另一个线程看到非 null 引用,却读取默认字段值。volatile 发布或使用静态内部类初始化可以建立构造完成到读取之间的顺序。 用 sleep 代替同步等待可见性:数据竞争检测结果:不可变对象通过 volatile/final 发布。 方案:更适合的场景:主要收益:代价与边界。 锁:多变量不变量与临界区:互斥和可见性一起保证:存在竞争与阻塞。 volatile 发布:独立状态或不可变对象发布:开销低、语义精确:不提供复合原子性。 final 安全初始化:构造后不变的对象图:正确构造后可安全读取 final 字段:this 逸出会破坏保证。 选型至少带上 并发线程数、临界区长度、阻塞比例和任务到达速率,并用上面的量化基线验证;