乐观锁是“先操作后校验”的并发控制策略,典型实现为CAS(如AtomicInteger.compareAndSet)或数据库version字段;悲观锁则提前加锁(如synchronized、ReentrantLock),适用于高冲突或强一致性场景。

乐观锁不是Java语言内置的锁,而是并发控制思想
Java里没有叫 OptimisticLock 的类或关键字——它是一种设计策略,核心是“先操作,后校验”。典型实现是用 AtomicInteger.compareAndSet() 或数据库的 version 字段。比如更新用户余额时,不加锁,而是读出当前值和版本号,提交时检查版本是否仍匹配,不匹配就重试。
- 常见错误:以为
synchronized或ReentrantLock加个标志位就是乐观锁——不是,那是手动模拟,且容易漏掉ABA问题 - 适用场景:读多写少、冲突概率低(如计数器、状态标记)
- 关键依赖:需要支持原子比较并交换(CAS)的底层能力,JVM通过
Unsafe.compareAndSwapInt暴露给java.util.concurrent.atomic包
悲观锁在Java里对应的是阻塞式同步机制
它假设并发冲突大概率发生,因此在操作前就加锁。Java中最直接的体现就是 synchronized 关键字和 ReentrantLock 类。它们会让线程在竞争失败时挂起(进入 WAITING 或 BLOCKED 状态),直到锁释放。
- 常见错误:在高并发短临界区场景下滥用
synchronized,导致线程频繁切换、上下文开销大 - 参数差异:
ReentrantLock支持公平/非公平模式、可中断等待(lockInterruptibly())、超时获取(tryLock(long, TimeUnit)),而synchronized全部不支持 - 性能影响:悲观锁在争抢激烈时可能引发明显延迟;但对长临界区、强一致性要求的场景(如转账扣款),反而比反复重试的乐观锁更稳
CAS操作本身有三大经典陷阱
即使你正确用了 AtomicInteger 或 AtomicReference,仍可能踩坑:
-
ABA问题:变量从A→B→A,CAS误判为“没变”。解决方式是用
AtomicStampedReference带版本戳,或改用AtomicMarkableReference -
循环时间长开销大:高争抢下
compareAndSet()反复失败+重试,CPU空转。可结合退避策略(如Thread.yield()或指数退避)缓解 - 只能保证单变量原子性:无法原子更新两个字段(如账户余额+更新时间)。此时要么升级为悲观锁,要么用
AtomicReference封装对象整体替换(注意对象不可变性)
选乐观还是悲观,关键看冲突率和操作粒度
别被概念带偏——没有银弹。实测发现:当写操作占比低于5%,且单次操作耗时<100ns时,乐观锁吞吐通常高出30%以上;一旦写冲突超过15%,或者临界区涉及IO或复杂计算,悲观锁的确定性优势立刻显现。
立即学习“Java免费学习笔记(深入)”;
最容易被忽略的一点:很多团队在用 MyBatis + MySQL 实现乐观锁时,只在SQL里加了 WHERE version = #{version},却忘了在更新成功后手动递增 version 字段——结果每次更新都因版本不匹配失败,还以为是并发太高。










