60 秒回答模板

我会先找共享可变状态、复合操作和对象发布点。最优先的办法是减少共享,比如不可变对象、线程封闭、ThreadLocal、消息队列或 actor 模型;必须共享时,再根据读写模式选择 synchronized、ReentrantLock、读写锁、CAS、原子类、并发容器或数据库乐观锁。并发安全不仅是互斥,还包括内存可见性、指令有序性、幂等和死锁规避,工程上还要控制锁粒度、持锁时间和压测验证。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

识别风险

并发问题通常来自多个线程读写同一份可变状态,尤其是 check-then-act、读改写、跨多个变量的不变式和未安全发布的对象。先定位临界区比直接加锁更重要。

02

减少共享

能不共享就不共享。不可变对象、局部变量、线程封闭、拷贝快照、消息传递和任务串行化都能从设计上降低并发复杂度。

03

同步手段

需要共享时,互斥锁适合保护复杂临界区,读写锁适合读多写少,CAS 和原子类适合简单原子更新,并发容器适合常见集合操作。选择要看冲突概率、临界区大小和可维护性。

04

内存语义

线程安全不只是不同时写。volatile、锁释放获取、final 安全发布和并发容器的 happens-before 关系,决定一个线程的修改能否被另一个线程正确看到。

05

工程边界

锁粒度过大会降低吞吐,锁顺序混乱会死锁,持锁执行 I/O 会放大延迟。实际系统还要有超时、降级、压测、竞态测试和生产指标来验证并发设计。

易错点

  • 看到并发问题就加全局锁,牺牲吞吐且掩盖真实临界区。
  • 只关注互斥,忽略可见性、有序性和安全发布。
  • 持锁执行网络 I/O、数据库调用或复杂回调,放大死锁和延迟风险。
  • 把并发容器的单次操作安全误认为组合操作也天然安全。

面试官追问

volatile 能保证线程安全吗?

它能保证可见性和一定有序性,但不能让 i++ 这类复合操作变成原子操作,多变量不变式仍需要锁或其他同步。

如何避免死锁?

固定加锁顺序,减少嵌套锁,避免持锁调用外部服务,使用 tryLock 超时,并把锁粒度和资源顺序设计清楚。

CAS 有什么问题?

CAS 适合低冲突简单更新,但可能自旋消耗 CPU,有 ABA 问题,也不适合一次维护多个变量的不变式。

什么时候不该加锁?

如果可以通过不可变数据、单线程串行化、队列或数据库唯一约束解决,就不应在业务代码里引入复杂锁。