主题
synchronized、ReentrantLock、原子类和并发容器应该怎样选择?
题型:追问 · 选择依据是不变量、竞争程度、阻塞能力和组合操作。
建议回答
如果只需要互斥保护一段临界区,优先考虑语义简单、自动释放的 synchronized。需要可中断获取、超时尝试、公平策略或多个 Condition 时,再使用 ReentrantLock,并在 finally 中释放。单变量或可表达成无锁状态机的更新可以用原子类和 CAS;高竞争计数可考虑 LongAdder,但它更适合统计,不适合要求每次读取都代表线性化精确值的业务余额。
并发容器解决的是容器内部操作的并发安全,例如 ConcurrentHashMap 的单次读写和 compute 类原子操作。若业务不变量跨多个键、多个容器或数据库,就仍需要更高层的锁、分段策略或事务。选择时还要看临界区是否可能执行 I/O:持锁做远程调用会放大阻塞,应先缩小锁范围或重构状态边界。
方案边界
- CAS 避免线程阻塞,但高竞争下会自旋消耗 CPU,还要处理 ABA 或状态版本问题。
- 公平锁可以减少饥饿,却通常降低吞吐;是否需要公平应由业务等待语义决定。
- 读多写少不意味着一定使用读写锁,临界区长度和实际竞争要通过压测或 JFR 验证。
进一步追问
synchronized解锁为什么能让后续加锁线程看到之前的写入?- CAS 成功为什么不等于一组业务操作整体原子?
易错点
- 不要笼统地说 CAS 一定比锁快,也不要把“无锁”理解成没有竞争成本。
- 不要用
size()、先检查再执行等多个线程安全操作拼出错误的复合逻辑。 - 不要为了展示 API 而使用显式锁;同步范围越复杂,遗漏释放和死锁风险越高。