news 2026/6/15 7:41:49

【大白话说Java面试题 第115题】【并发篇】第15题:说一下悲观锁和乐观锁的区别?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【大白话说Java面试题 第115题】【并发篇】第15题:说一下悲观锁和乐观锁的区别?

📌异常处理:Java开发基于Spring Boot的异常处理框架设计:电商系统业务异常建模与全局统一响应实现

第15题:说一下悲观锁和乐观锁的区别?

📚回答:

  • 核心考点: 悲观锁与乐观锁的区别不是"加锁 vs 不加锁"这么简单,而是两种完全不同的并发控制哲学。大厂面试不会只问"有什么区别",而是深入考察冲突概率的量化判断(什么阈值下切换策略)、实现路径的底层差异(CPU 指令 vs 应用层版本号)、以及混合策略的工程实践(读乐观 + 写悲观、本地 CAS + 数据库版本号)。面试官真正想判断的是:你是否能建立从硬件到业务的完整认知,并在复杂场景下做出正确选型。
1. 核心思想对比——两种并发控制哲学
维度悲观锁(Pessimistic Lock)乐观锁(Optimistic Lock)
核心假设冲突是常态,先加锁再操作冲突是少数,先操作再检测
控制时机访问前加锁提交时检测
阻塞行为其他线程阻塞等待其他线程不阻塞,失败重试
一致性保障物理阻塞保证版本号/CAS 检测保证
适用冲突率> 30%< 20%
心智模型“先占坑,再办事”“先办事,冲突了再重来”

类比理解

  • 悲观锁 = 去银行排队,先到窗口占住位置(加锁),办完才走;
  • 乐观锁 = 去银行取号,叫到号时如果发现前面有人插队(版本号变了),重新取号再排。
2. 实现机制对比——从 CPU 到数据库的全链路差异
  • 2.1 Java 层面的实现

    特性悲观锁乐观锁
    代表实现synchronizedReentrantLockAtomicIntegerLongAdderStampedLock
    底层机制Monitor(_owner+_EntryListCAS(lock cmpxchg
    线程状态RUNNABLE → BLOCKED/WAITING → RUNNABLE始终 RUNNABLE(自旋或成功)
    上下文切换有(内核态切换 ~1-10ms)无(纯用户态 ~10-100ns)
    内存开销Monitor 对象 + 队列节点无额外对象(除Atomic包装)

    代码对比

    // ========== 悲观锁:synchronized ==========privateintcount=0;publicsynchronizedvoidincrement(){count++;// 获取 Monitor → 执行 → 释放 Monitor}// 字节码:monitorenter + getfield + iadd + putfield + monitorexit// 涉及:用户态→内核态切换、线程队列、操作系统调度// ========== 乐观锁:CAS ==========privateAtomicIntegercount=newAtomicInteger(0);publicvoidincrement(){count.incrementAndGet();// lock cmpxchg 自旋直到成功}// 字节码:getfield + loop(getvolatile + cmpxchg) + putfield// 涉及:纯 CPU 指令,无内核态切换
  • 2.2 数据库层面的实现

    特性悲观锁乐观锁
    SQL 语法SELECT ... FOR UPDATEUPDATE ... WHERE version = ?
    锁类型行锁 / 间隙锁 / 表锁无锁,版本号检测
    隔离级别依赖依赖 RR/RC与应用层隔离级别无关
    死锁风险有(需检测和回滚)
    重试责任数据库(锁等待)应用层(版本冲突抛异常)

    SQL 对比

    -- ========== 悲观锁 ==========BEGIN;SELECTstock,versionFROMproductWHEREid=1FORUPDATE;-- 加行锁-- 业务计算UPDATEproductSETstock=stock-1WHEREid=1;-- 锁内更新COMMIT;-- 释放锁-- ========== 乐观锁 ==========BEGIN;SELECTstock,versionFROMproductWHEREid=1;-- 无锁读取-- 业务计算UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDversion=5;-- 提交时检测版本-- 影响行数 = 0 表示冲突,应用层重试COMMIT;
  • 2.3 分布式层面的实现

    特性悲观锁乐观锁
    实现方式Redis RedLock、ZooKeeper、数据库分布式锁版本号 + CAS、MVCC、Saga 模式
    协调成本高(需要中心节点)低(无中心协调)
    网络开销每次加锁/解锁需网络 RTT提交时一次网络 RTT
    典型场景分布式任务调度、库存扣减分布式配置、最终一致性事务
3. 性能模型对比——冲突概率决定胜负
  • 3.1 理论性能曲线
    假设 100 线程并发,执行 100 万次累加:

    冲突概率悲观锁(synchronized)乐观锁(AtomicLong)乐观锁(LongAdder)
    0%2.5s1.2s1.5s
    5%2.8s1.5s1.6s
    20%4.0s3.5s2.0s
    50%8.0s12.0s(自旋风暴)3.0s
    80%15.0s60.0s+(活锁)5.0s
    99%30.0s(串行化)不可用8.0s

    关键结论

    • 低冲突(< 20%):乐观锁性能碾压悲观锁(无上下文切换);
    • 高冲突(> 50%):悲观锁更稳定(线程阻塞释放 CPU),乐观锁自旋导致 CPU 打满;
    • 极端冲突(> 90%):两者都退化,需队列化(单线程串行处理)。
  • 3.2 延迟分布对比

    百分位悲观锁(P99)乐观锁(P99)说明
    P502ms0.1μs乐观锁无阻塞,延迟极低
    P905ms0.5μs悲观锁受线程调度影响
    P9950ms10ms+乐观锁高冲突时重试累积
    P99.9200ms100ms+悲观锁锁等待超时风险
4. 功能特性对比——不仅仅是性能
特性悲观锁乐观锁
死锁有(需预防/检测)
写偏斜无(锁范围保护)有(需 Serializable 或业务补偿)
ABA 问题有(CAS 路径)
可重入支持(ReentrantLock不支持(CAS 无持有概念)
公平性可配置(公平/非公平)天然非公平(随机成功)
条件变量支持(Condition不支持
超时获取支持(tryLock(timeout)不支持(自旋或立即失败)
中断响应支持(lockInterruptibly不支持
批量操作容易(锁内多行操作)困难(需事务包裹)
跨行一致性容易(锁多行)困难(多版本号管理)
5. 适用场景对比——从业务维度选型
  • 5.1 悲观锁的主战场

    场景原因典型实现
    金融转账强一致性,零容忍数据不一致SELECT FOR UPDATE+ 事务
    库存扣减写冲突高,需串行化分布式锁 + 数据库行锁
    订单状态机状态流转需严格顺序synchronized+ 状态校验
    全局 ID 生成必须唯一且连续数据库号段模式(Leaf)
    分布式任务调度同一任务只能一个节点执行ZooKeeper 分布式锁
  • 5.2 乐观锁的主战场

    场景原因典型实现
    商品详情页浏览读多写少(1000:1)无锁读取 + 缓存
    用户积分查询低频更新,高频查询版本号 + 缓存
    配置中心读取几乎无写,海量读CopyOnWriteArrayList
    计数器/统计高并发累加,允许估算LongAdder
    分布式配置更新最终一致性即可CAS + 版本号
  • 5.3 混合策略场景

    场景策略说明
    读写分离系统读乐观 + 写悲观读走缓存无锁,写走数据库加锁
    秒杀系统本地 CAS + 数据库乐观锁Redis 预减(乐观)+ 数据库兜底(悲观)
    缓存一致性乐观更新 + 异步补偿CAS 更新缓存,MQ 异步同步数据库
6. 工程选型决策树
是否需要强一致性(如金融、库存)? ├── 是 → 悲观锁 │ └── 单机 or 分布式? │ ├── 单机 → synchronized / ReentrantLock │ └── 分布式 → Redis RedLock / ZooKeeper / 数据库分布式锁 └── 否 → 冲突概率评估 ├── < 5%(读多写少)→ 乐观锁 │ ├── 单机计数 → AtomicLong / LongAdder │ ├── 数据库更新 → 版本号字段 │ └── 分布式配置 → CAS + 版本号 ├── 5%~30%(读写均衡)→ 混合策略 │ ├── 读:无锁 / 乐观锁 │ └── 写:悲观锁 / 队列化 └── > 30%(写多读少)→ 悲观锁 + 优化 ├── 锁粒度细化(行锁替代表锁) ├── 读写分离(ReadWriteLock) └── 队列化串行(Disruptor / 单线程)
7. 常见误区澄清
误区正确理解
“乐观锁不加锁,所以一定更快”❌ 高冲突下乐观锁自旋导致 CPU 100%,可能比悲观锁更慢
“悲观锁就是 synchronized”❌ 悲观锁是思想,synchronized 只是 JVM 实现之一,数据库FOR UPDATE也是悲观锁
“乐观锁只能用于数据库”❌ JavaAtomic类、StampedLock 都是乐观锁实现
“悲观锁一定保证一致性”❌ 未正确使用事务隔离级别或锁粒度,仍可能出现幻读、不可重复读
“高并发必须用乐观锁”❌ 秒杀等高冲突场景,乐观锁重试风暴会导致系统崩溃
“乐观锁无死锁”✅ 正确,但可能有活锁(无限重试)和饥饿(某些线程一直失败)
8. 生产环境避坑指南
  • 8.1 避免"一刀切"选型

    // ❌ 错误:所有场景都用 synchronizedpublicclassBadDesign{privateMap<String,Config>configs=newHashMap<>();publicsynchronizedConfiggetConfig(Stringkey){returnconfigs.get(key);// 读操作也加锁!}}// ✅ 正确:读用乐观,写用悲观publicclassGoodDesign{privatevolatileMap<String,Config>configs=newHashMap<>();publicConfiggetConfig(Stringkey){returnconfigs.get(key);// 读:无锁,volatile 保证可见性}publicsynchronizedvoidupdateConfig(Stringkey,Configconfig){Map<String,Config>newConfigs=newHashMap<>(configs);newConfigs.put(key,config);configs=newConfigs;// 写:CopyOnWrite 思想}}
  • 8.2 监控冲突率,动态调整策略

    // 埋点监控乐观锁冲突率CounterconflictCounter=meterRegistry.counter("optimistic.lock.conflict");publicbooleanupdateWithVersion(Productproduct){introws=productDao.update(product);if(rows==0){conflictCounter.increment();// 冲突率 > 20% 时告警,提示改用悲观锁returnfalse;}returntrue;}
  • 8.3 数据库乐观锁必须配合索引

    -- ❌ 错误:version 无索引,全表扫描UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDversion=5;-- id 是主键,OK-- ❌ 危险:按非索引字段更新UPDATEproductSETstock=stock-1,version=version+1WHEREsku='ABC123'ANDversion=5;-- sku 无索引 → 全表扫描 + 表锁!
  • 8.4 分布式锁必须设置超时

    // ❌ 错误:无超时,死锁后永久阻塞lock.lock();// ✅ 正确:超时 + 看门狗续期if(lock.tryLock(10,TimeUnit.SECONDS)){try{/* 业务 */}finally{lock.unlock();}}
9. 面试官追问与高分回答模板
  • 追问 1:“悲观锁和乐观锁的区别是什么?”

    • 低分回答:“悲观锁加锁,乐观锁不加锁用版本号。”(太浅,没有触及本质)
    • 高分回答

      "悲观锁和乐观锁是两种完全不同的并发控制哲学:

      1. 核心假设:悲观锁假设冲突是常态,先加锁再操作;乐观锁假设冲突是少数,先操作再检测。
      2. 实现机制:悲观锁通过物理阻塞(Monitor、数据库行锁)保证独占;乐观锁通过冲突检测(CAS、版本号)保证一致性。
      3. 性能特征:低冲突时乐观锁性能碾压(无上下文切换),高冲突时悲观锁更稳定(线程阻塞释放 CPU)。
      4. 功能差异:悲观锁支持可重入、条件变量、超时获取;乐观锁天然非公平、无阻塞、无死锁但可能有活锁。

      选型的唯一金标准是冲突概率:< 20% 用乐观锁,> 30% 用悲观锁,中间地带用混合策略。"

  • 追问 2:“什么场景下乐观锁比悲观锁慢?”

    • 高分回答

      "高冲突场景(> 50%)下乐观锁会比悲观锁慢,原因有三:

      1. 自旋风暴:CAS 失败率高时,线程 100% CPU 空转,但业务吞吐量几乎为 0;
      2. 缓存行竞争:多核同时 CAS 同一变量,缓存行在核心间频繁’乒乓’,总线饱和;
      3. 活锁:所有线程同时读取、同时 CAS、同时失败,循环往复。

      量化数据:100 线程并发,冲突率 80% 时,AtomicLong 的吞吐量可能只有 synchronized 的 1/10,且 CPU 使用率 100%。此时悲观锁的线程阻塞反而释放了 CPU 资源,整体吞吐量更高。"

  • 追问 3:“数据库中悲观锁和乐观锁怎么选?”

    • 高分回答

      "数据库层面的选型同样取决于冲突概率和一致性要求:

      1. 读多写少(< 5% 冲突):乐观锁(版本号)。例如商品详情页,读:写 = 1000:1,加FOR UPDATE会阻塞大量读线程;
      2. 读写均衡(5%~30%):混合策略。读走主从复制(无锁),写走悲观锁(FOR UPDATE)或乐观锁 + 重试;
      3. 写多读少(> 30%):悲观锁。例如库存扣减,冲突率高,乐观锁重试风暴会导致数据库 CPU 打满;
      4. 强一致性(金融转账):悲观锁 + Serializable 隔离级别,或分布式事务(Seata XA)。

      关键细节:乐观锁的UPDATE ... WHERE version = ?必须确保 WHERE 条件走索引,否则退化为全表扫描,效果等同于表锁。"

  • 追问 4:“乐观锁的 ABA 问题,悲观锁有吗?”

    • 高分回答

      "悲观锁没有 ABA 问题,因为悲观锁通过物理阻塞确保操作期间没有其他线程修改数据。

      乐观锁的 ABA 问题分两种实现:

      1. CAS 路径AtomicReference存在 ABA,因为 CAS 只比较值,不比较修改历史。解决用AtomicStampedReference(版本号);
      2. 版本号路径:数据库乐观锁的version字段递增,天然解决 ABA(值回退但版本号不同)。

      所以 ABA 是 CAS 特有的问题,版本号乐观锁和悲观锁都不存在。"

  • 追问 5:“分布式环境下,悲观锁和乐观锁怎么选?”

    • 高分回答

      "分布式环境下,两者的实现和权衡都发生了变化:

      悲观锁的分布式化

      • 单机synchronized失效,需引入 Redis RedLock、ZooKeeper 分布式锁;
      • 代价:网络 RTT(~1-5ms)、时钟漂移风险(RedLock)、脑裂风险;
      • 适用:必须强互斥的场景(如分布式任务调度、全局 ID 生成)。

      乐观锁的分布式化

      • 数据库版本号天然支持分布式(无中心协调);
      • 代价:冲突检测在提交时,网络往返后才发现冲突,重试成本更高;
      • 适用:最终一致性场景(如配置更新、缓存同步)。

      现代趋势:分布式场景下,两者都在向’无锁化’演进——Saga 模式(最终一致性)、CRDT(无锁数据结构)、MVCC(多版本并发控制)正在替代传统的锁方案。"

  • 追问 6:“如果让你设计一个秒杀系统,你会怎么选锁?”

    • 高分回答

      "秒杀系统是’极端高冲突’场景(万人抢 100 件商品,冲突率 99.99%),传统锁方案都不适用,需要分层解耦

      1. 流量层:Nginx + Lua 限流,99% 请求直接拒绝,只剩 1% 进入后端;
      2. 缓存层:Redisdecr预减库存(原子操作,无锁),库存为 0 直接返回’已售罄’;
      3. 消息队列:通过 MQ 异步下单,队列化串行处理,彻底消除并发冲突;
      4. 数据库层:最终一致性写入,用乐观锁(版本号)兜底,冲突率已极低;
      5. 降级策略:Redis 降级为本地缓存,MQ 降级为直接写库 + 悲观锁(最后防线)。

      核心思想:不是’选哪种锁’,而是’让冲突不要发生’。通过限流、缓存、队列化三层过滤,将数据库层面的冲突率从 99.99% 降到 < 1%,此时乐观锁轻松应对。"

10. 方案选型速查表
场景冲突率一致性推荐方案不推荐方案
商品详情页浏览< 1%最终一致无锁 + 缓存任何锁
用户配置读取< 1%最终一致CopyOnWriteArrayList读写锁
账户余额查询< 5%强一致乐观锁(版本号)悲观锁
库存查询< 5%强一致乐观锁(版本号)悲观锁
积分累加5%~20%最终一致LongAdder+ 异步落库AtomicLong
订单创建20%~50%强一致悲观锁(行锁)纯乐观锁
库存扣减(普通)30%~50%强一致悲观锁 + 索引优化乐观锁
秒杀库存扣减> 99%强一致Redis 预减 + MQ 队列化任何数据库锁
金融转账任意强一致悲观锁 + 事务乐观锁
分布式任务调度强一致ZooKeeper 分布式锁Redis 锁
全局配置更新极低最终一致CAS + 版本号分布式锁

💡面试官想要的满分总结

悲观锁与乐观锁的区别不是"加锁 vs 不加锁",而是两种并发控制哲学的根本分歧:悲观锁"先占坑再办事",用物理阻塞保证强一致;乐观锁"先办事再检查",用冲突检测换取高并发。

选型的唯一金标准是冲突概率:< 20% 时乐观锁性能碾压(无上下文切换、无内核态切换),> 30% 时悲观锁更稳定(线程阻塞释放 CPU、避免自旋风暴)。但两者都不是银弹——极端冲突下(> 90%),任何锁都会退化,必须通过限流、缓存、队列化从根本上消除冲突。

工程实践中,混合策略是主流:读多写少场景读走乐观/无锁、写走悲观;秒杀等高冲突场景通过 Redis 预减 + MQ 队列化将数据库冲突率降到 < 1%;分布式环境下,Saga 模式和 MVCC 正在替代传统锁方案。最后记住:最好的锁是不用锁——通过架构设计让冲突不要发生,比选哪种锁更重要。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 7:39:51

XUnity自动翻译器完整指南:5分钟让Unity游戏支持中文翻译

XUnity自动翻译器完整指南&#xff1a;5分钟让Unity游戏支持中文翻译 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 还在为外语游戏而烦恼吗&#xff1f;XUnity自动翻译器是你的完美解决方案&#xff01…

作者头像 李华
网站建设 2026/6/15 7:31:51

思维图(GoT):突破思维链瓶颈的网状推理工程实践

1. 这不是概念炒作&#xff0c;而是模型推理能力的真实跃迁“Chain of Thought”&#xff08;思维链&#xff09;这个词&#xff0c;我第一次在2022年夏天的arXiv论文里看到时&#xff0c;还把它当成又一个学术圈自嗨的术语。直到我用GPT-3.5写一段Python脚本调试逻辑错误——它…

作者头像 李华