Appearance
并发与锁
并发是后端面试的深度考察区:从锁的种类到线程池参数,再到 IO 模型,层层递进。
Q1: 悲观锁和乐观锁的区别? 「🟢 校招/初级」
考察点:并发控制的基本思路。
参考答案:
- 悲观锁:假设一定会冲突,先加锁再操作。代表:
synchronized、ReentrantLock、数据库行锁。适合写多冲突频繁的场景。 - 乐观锁:假设冲突少,不加锁,提交时校验版本(CAS / version 字段)。适合读多写少。
- CAS 的三个问题:ABA 问题(加版本号解决)、自旋开销、只能保证单个变量原子性。
追问延伸:
- Java 的 CAS 底层靠什么指令?(CPU 的 CMPXCHG + 总线锁/缓存锁)
- 数据库乐观锁的 version 字段怎么实现?
Q2: synchronized 和 ReentrantLock 的区别? 「🟡 中级」
考察点:Java 锁机制的细节(其他语言有对应问法)。
参考答案:
synchronized:JVM 内置,自动加锁解锁;锁升级路径:无锁 → 偏向锁 → 轻量级锁(CAS 自旋)→ 重量级锁(OS 互斥量)。ReentrantLock:API 层面的锁,支持公平锁、可中断、超时尝试、多个 Condition;必须手动在 finally 中解锁。- 选型:简单场景用
synchronized(JIT 优化好);需要高级特性时用ReentrantLock。
追问延伸:
- 锁升级的过程可以降级吗?
- 为什么 JDK 15 之后偏向锁默认禁用了?
Q3: volatile 的作用?能保证原子性吗? 「🟡 中级」
考察点:内存可见性与指令重排。
参考答案:
- 可见性:写操作立即刷回主内存,读操作强制从主内存加载(通过内存屏障实现)。
- 禁止重排:典型场景是双重检查锁单例。
- 不保证原子性:
i++是读-改-写三步,仍需要原子类或锁。
追问延伸:
- Java 内存模型(JMM)的 happens-before 规则能说出几条?
- Go 里对应可见性保证的手段是什么?(channel / atomic / mutex)
Q4: 线程池的核心参数和工作流程? 「🟡 中级」
考察点:线程池几乎是 Java 后端必问题。
参考答案:
七大参数:核心线程数、最大线程数、空闲存活时间、时间单位、任务队列、线程工厂、拒绝策略。
工作流程:
- 核心线程未满 → 创建核心线程执行。
- 核心线程满 → 任务入队列。
- 队列满 → 创建非核心线程(直到最大线程数)。
- 都满了 → 执行拒绝策略(Abort/CallerRuns/Discard/DiscardOldest)。
- 参数设置:CPU 密集型 ≈ 核数+1;IO 密集型 ≈ 核数×2(实际要压测)。
- 坑:
Executors.newFixedThreadPool用无界队列可能 OOM,阿里规约要求手动创建线程池。
追问延伸:
- 为什么任务先入队而不是先开非核心线程?
- 怎么监控线程池运行状态?
Q5: BIO、NIO、AIO 的区别? 「🟡 中级」
考察点:IO 模型演进,Netty/Redis 高并发的基础。
参考答案:
- BIO:同步阻塞,一个连接一个线程,并发低。
- NIO:同步非阻塞 + 多路复用(select/poll/epoll),一个线程管理多个连接;Linux epoll 是 Redis、Nginx、Netty 的基石。
- AIO:异步非阻塞,内核完成后回调通知(Linux 支持不完善,Windows IOCP 成熟),实际应用少。
追问延伸:
- select、poll、epoll 的区别?(数组上限/轮询开销/事件驱动)
- epoll 的 ET 和 LT 模式?
Q6: 什么是 CAS?Atomic 类是怎么实现的? 「🟡 中级」
考察点:无锁并发原语。
参考答案:
- CAS(Compare And Swap):比较内存值与预期值,相同才写入新值,整个过程是 CPU 原子指令。
- Java 的
AtomicInteger等基于Unsafe.compareAndSwap,失败自旋重试。 - 局限:ABA(用
AtomicStampedReference加版本号)、高冲突时自旋浪费 CPU、只能处理单个变量(多变量用锁或AtomicReference包装)。
追问延伸:
- LongAdder 比 AtomicLong 快在哪?(分段累加减少争用)
- Go 的
sync/atomic和 Java 的有什么区别?
Q7: 如何排查一次死锁? 「🟡 中级」
考察点:实战排障能力。
参考答案:
- Java:
jstack <pid>直接输出死锁链(会明确提示 "Found one Java-level deadlock");图形工具可用 JConsole/Arthas 的thread -b。 - 数据库:查看 InnoDB 的死锁日志(
SHOW ENGINE INNODB STATUS),通常会回滚代价小的事务。 - 预防:全局固定加锁顺序、缩小锁粒度、加锁超时。
追问延伸:
- 生产环境不方便 jstack 怎么办?(Arthas、提前接监控告警)
- 分布式环境下的"死锁"怎么发现?(链路追踪 + 超时兜底)