Skip to content

并发与锁

并发是后端面试的深度考察区:从锁的种类到线程池参数,再到 IO 模型,层层递进。

Q1: 悲观锁和乐观锁的区别? 「🟢 校招/初级」

考察点:并发控制的基本思路。

参考答案

  • 悲观锁:假设一定会冲突,先加锁再操作。代表:synchronizedReentrantLock、数据库行锁。适合写多冲突频繁的场景。
  • 乐观锁:假设冲突少,不加锁,提交时校验版本(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 后端必问题。

参考答案

七大参数:核心线程数、最大线程数、空闲存活时间、时间单位、任务队列、线程工厂、拒绝策略。

工作流程:

  1. 核心线程未满 → 创建核心线程执行。
  2. 核心线程满 → 任务入队列。
  3. 队列满 → 创建非核心线程(直到最大线程数)。
  4. 都满了 → 执行拒绝策略(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、提前接监控告警)
  • 分布式环境下的"死锁"怎么发现?(链路追踪 + 超时兜底)