面试题随笔
1) volatile
保证了什么?DCL(双重检查锁)为什么要配合 volatile
?
答案要点:
-
volatile
提供可见性与禁止指令重排(针对该变量的写-读),不提供原子性(除 long/double 读写在 64 位上已原子)。 -
DCL 中
instance = new Obj()
可能被重排为:- 分配内存 → 2) 赋值引用 → 3) 调构造
线程 B 看到非 null 引用但对象未构造完。给instance
加volatile
可禁止这一步的写-读重排。
- 分配内存 → 2) 赋值引用 → 3) 调构造
-
适用场景:配置热更新标志、一次性发布(publish-once)等;不适合计数累加(需
Atomic*
或锁)。
2) synchronized
vs ReentrantLock
vs StampedLock
区别?
答案要点:
synchronized
:JVM 指令层支持,可重入、不可中断、无超时;偏向/轻量/重量级自适应;JIT 可消除锁。ReentrantLock
:可中断、可超时、公平/非公平可选;Condition
多条件队列;更灵活的可见性边界。StampedLock
:读写/乐观读(tryOptimisticRead
),不可重入、不可中断写锁;适合读多写少,注意死锁/饥饿与finally
解锁。
3) 线程池怎么合理配置?有何常见坑?
答案要点:
-
参数:
corePoolSize / maximumPoolSize / keepAlive / queue / threadFactory / handler
。
队列选型:CPU 密集→ 小队列;IO 密集→ 较大队列。拒绝策略常用CallerRunsPolicy
(削峰+回压)。 -
大坑:
- 使用
Executors.newFixedThreadPool()
(无界队列)与newCachedThreadPool()
(无界最大线程)易 OOM/抖动。 - 线程命名缺失、无监控(任务堆积难排查)。
- 上下文丢失(MDC/TraceId)——用装饰器或
TaskDecorator
传递。
- 使用
-
粗略估算:CPU 密集
Nthreads≈CPU核数 or 核数+1
;IO 密集≈ CPU核数 * (1 + 等待/计算)
;最好基于压测与指标调参。
4) CompletableFuture
组合常见模式与坑?
答案要点:
- 组合:
thenCompose
(串联依赖,扁平化) vsthenApply
(一般映射) vsthenCombine
(汇合多源);allOf/anyOf
。 - 线程池:默认使用
ForkJoinPool.commonPool()
;推荐显式提供业务线程池(避免和 CPU 任务抢池)。 - 异常:
exceptionally/handle/whenComplete
差异;join()
会把受检异常包装为CompletionException
。 - 超时/取消:
orTimeout/completeOnTimeout/cancel(true)
;注意取消的可中断性取决于任务实现。
5) G1 / ZGC / Shenandoah 对比与调优入门?
答案要点:
- G1:分区化 Region,并发标记,按回收价值优先,可预测停顿(
-XX:MaxGCPauseMillis
目标);默认收集器(较新 JDK)。 - ZGC/Shenandoah:低停顿(ms 级),基于着色指针/负载屏障(ZGC)或Brooks pointer(Shenandoah),适合大堆场景。
- 基本调优:设定堆大小、暂停目标、观察 GC 日志(JDK9+
-Xlog:gc*
),看晋升失败、混合回收频率、停顿分解。 - 选择:延迟敏感/大堆→ ZGC/Shen;通用场景→ G1 更稳。
6) InnoDB 的隔离级别与 MVCC 如何避免幻读?
答案要点:
- 隔离级别:
READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ(默认) / SERIALIZABLE
。 - InnoDB 的 RR + MVCC 可避免不可重复读;幻读通过间隙锁/Next-Key Lock在当前读(如
SELECT ... FOR UPDATE
/UPDATE/DELETE
)下避免。 - 快照读:普通
SELECT
读到一致性视图;当前读:修改/锁定语句读最新且加锁。
7) 分布式事务:2PC / TCC / Saga / Outbox 各适用何种业务?
答案要点:
- 2PC:强一致,协调器两阶段提交,阻塞/单点/长事务风险;多用于同构存储或强一致核心。
- TCC:Try/Confirm/Cancel 接口,业务侵入强,适合库存/资金扣减等明确补偿模型。
- Saga:长事务拆分为一系列本地事务 + 反向补偿动作,最终一致,适合长流程;要处理乱序/幂等。
- Outbox + 消息:本地事务写业务数据和事件表,异步投递(或 binlog 同步)以实现最终一致;结合去重键/幂等落地“至少一次”。
8) 缓存一致性与三大问题:穿透/击穿/雪崩,怎么解?
答案要点:
- 穿透(DB 没有的数据被反复查):布隆过滤器/缓存空值/接口层限流。
- 击穿(热点 Key 失效瞬间并发打到 DB):互斥锁/单飞(
SETNX
或本地锁)、逻辑过期+异步重建。 - 雪崩(大量 Key 同时失效):随机 TTL、多级缓存、限流降级。
- 一致性策略:Cache-Aside(最通用;先更新 DB、再删缓存,或“延时双删”);Write-Through/Behind(写入时同步/异步落盘);订阅 binlog 精准失效。
9) 类加载与 SPI:为什么常用线程上下文类加载器(TCCL)?
答案要点:
- 父优先下,父层 API(JDBC/JNDI)需要加载子层实现(位于应用/插件 ClassLoader 可见范围)。
- 解决:在调用前
Thread.currentThread().setContextClassLoader(appCL)
,让 API 用 TCCL 去发现实现(或ServiceLoader.load(..., loader)
)。 - 常见坑:忘记恢复导致类/ClassLoader 泄漏;不同加载器同名类导致
ClassCastException/instanceof false
。
10) 微服务韧性设计:重试、熔断、限流怎么配?
答案要点:
- 重试:幂等(业务幂等键/去重表);指数退避 + 抖动(避免惊群);区分可重试与不可重试异常。
- 熔断:基于错误率/慢调用比打开;半开探测;结合隔离(线程池/信号量)与超时。
- 限流:令牌桶/漏桶;按用户/商户/资源维度;返回可降级响应。
- 实用栈:
resilience4j
(Retry/CircuitBreaker/RateLimiter/Bulkhead/TimeLimiter);网关限流 + 服务内细粒度保护。