乐观锁 vs 悲观锁 含面试模板

乐观锁 vs 悲观锁 知识总结

一、基础定义

乐观锁

  • 核心思想 :操作数据时乐观假设其他线程不会同时修改,不主动加锁,仅在更新时校验数据是否被改动。
  • 本质:无锁并发控制,通过"版本校验"保证数据一致性。

悲观锁

  • 核心思想 :操作数据时悲观假设其他线程一定会修改,操作前先加锁,其他线程需阻塞等待锁释放。
  • 本质:独占锁机制,通过"阻塞等待"保证数据一致性。

二、执行流程

乐观锁大体流程

  1. 多线程(如线程A、B)直接获取共享数据,不加锁执行各自操作。
  2. 更新数据前,先校验数据是否被其他线程修改。
  3. 若数据未被修改:直接更新内存中的数据值。
  4. 若数据已被修改:根据业务需求选择报错重试更新

悲观锁大体流程

  1. 多线程(如线程A、B)尝试获取同一同步锁。
  2. 若线程A先获取锁并执行操作,线程B进入阻塞等待状态,直到A释放锁。
  3. 线程A释放锁后,CPU唤醒等待线程(如线程B),线程B再次尝试获取锁。
  4. 线程B成功获取锁后,执行自身操作。

三、实现方式

乐观锁的实现

1. CAS 实现
  • Java 中 java.util.concurrent.atomic 包下的原子变量(如 AtomicInteger)采用 CAS 实现乐观锁。
  • 核心逻辑:Compare And Swap(比较并交换),先比较内存值与预期值,一致则更新,不一致则重试。
2. Version 版本号机制(数据库场景常用)
  • 数据表新增 version 字段,记录数据修改次数,数据更新时 version 自动 +1。

  • 执行流程:

    1. 查询数据时,同时读取 version 值。
    2. 更新时,携带读取到的 version 值,仅当数据库中 version 与传入值一致时才执行更新,并将 version +1。
  • SQL 示例:

    sql 复制代码
    UPDATE user 
    SET name = "zs", version = version + 1 
    WHERE id = 1 AND version = oldVersion;

悲观锁的实现

1. 数据库层面
  • 传统关系型数据库的锁机制:行锁、表锁、读锁、写锁等,均在操作前加锁。

  • SQL 示例(MySQL 行锁):

    sql 复制代码
    SELECT * FROM user WHERE id = 1 FOR UPDATE;
2. Java 语言层面
  • 独占锁实现:synchronized 关键字、ReentrantLock 可重入锁。
  • 特点:线程获取锁后,其他线程必须阻塞等待,直到锁被释放。

四、优缺点与适用场景

乐观锁

  • 优点:无锁竞争,读操作性能极高,适合高并发读场景。
  • 缺点:写冲突频繁时,重试次数多会降低性能;无法解决 ABA 问题(CAS 场景)。
  • 适用场景读多写少的业务场景(如商品详情查询、统计报表),追求读性能。

悲观锁

  • 优点:强一致性保证,写操作数据安全,适合并发写入频繁场景。
  • 缺点:锁竞争导致线程阻塞,并发性能低;存在死锁风险。
  • 适用场景写多读少的业务场景(如库存扣减、订单支付),追求数据正确性。

五、核心对比总结

维度 乐观锁 悲观锁
核心思想 假设无冲突,更新时校验 假设必冲突,操作前加锁
加锁时机 无锁,更新时校验 操作前加锁
性能表现 读性能高,写冲突时性能下降 读写性能均受锁竞争影响,并发低
数据一致性 弱一致性,依赖版本校验 强一致性,锁独占保证
典型实现 CAS、Version 版本号 数据库行锁、synchronized、ReentrantLock
适用场景 读多写少(如查询、统计) 写多读少(如库存、支付)

面试模板

乐观锁 vs 悲观锁 选型指南(面试/开发通用)

核心选型原则

选择的核心逻辑:以"读写比例"和"业务一致性要求"为核心,兼顾性能与数据安全,以下是可直接落地的选型步骤和判断标准。


一、第一步:先判断核心场景(读写比例)

场景1:读多写少(90%读 + 10%写)→ 优先选乐观锁

典型业务场景
  • 电商商品详情页查询、新闻资讯浏览、用户信息展示;
  • 统计报表、数据看板(仅偶尔更新基础数据);
  • 缓存预热、非核心数据的批量查询。
选型理由
  • 乐观锁无锁竞争,读操作无需等待,能最大化提升并发读性能;
  • 写操作占比低,冲突概率小,少量重试/报错不会影响整体业务。
落地示例
  • Java 代码:用 AtomicInteger 处理计数器(如商品浏览量统计);
  • 数据库:用 Version 版本号机制(如用户昵称修改,冲突概率极低)。

场景2:写多读少(90%写 + 10%读)→ 优先选悲观锁

典型业务场景
  • 电商秒杀库存扣减、订单状态更新;
  • 金融账户余额变动、交易流水写入;
  • 秒杀/抢购、限量优惠券发放。
选型理由
  • 写操作频繁,乐观锁会因频繁冲突导致大量重试,反而降低性能;
  • 悲观锁通过独占锁保证写操作的原子性,避免数据脏写/覆盖,符合强一致性要求。
落地示例
  • Java 代码:用 synchronized/ReentrantLock 处理库存扣减;
  • 数据库:用 SELECT ... FOR UPDATE 加行锁(如扣减库存前锁定订单行)。

二、第二步:补充判断业务一致性要求

要求1:强一致性(不允许数据不一致)→ 优先悲观锁

核心特征
  • 涉及资金、核心交易、合规性要求高的场景;
  • 数据错误会导致直接经济损失或合规风险(如转账、支付、税务数据)。
示例
  • 银行转账:A账户扣钱、B账户加钱必须原子性完成,绝对不允许中间状态;
  • 秒杀库存:必须保证库存扣减准确,不超卖、不少卖。

要求2:弱一致性(允许短暂不一致,最终一致即可)→ 优先乐观锁

核心特征
  • 非核心数据、允许少量重试的场景;
  • 数据不一致可通过后续补偿/重试修复(如用户积分、商品浏览量)。
示例
  • 商品点赞数:短暂的计数偏差可接受,最终同步即可;
  • 个人资料修改:即使冲突重试,对用户体验影响极小。

三、第三步:考虑技术成本与性能瓶颈

乐观锁的技术成本&风险

  • 优点:实现简单(如 MyBatis-Plus 乐观锁插件一键配置)、无死锁风险;
  • 缺点:需处理重试逻辑(如 CAS 自旋),高并发写场景下重试次数过多会导致 CPU 飙升;
  • 避坑点 :CAS 存在 ABA 问题,需通过版本号/时间戳解决(如 AtomicStampedReference)。

悲观锁的技术成本&风险

  • 优点:逻辑简单,无需处理冲突重试,数据安全;
  • 缺点:存在死锁风险(需规范加锁顺序)、高并发下线程阻塞导致性能下降;
  • 避坑点 :数据库悲观锁(如 FOR UPDATE)需注意锁粒度,避免锁表(优先行锁)。

四、选型决策表(直接对照)

场景特征 推荐选型 典型落地方式
读多写少 + 弱一致性 乐观锁 MyBatis-Plus 版本号/Atomic 原子类
写多读少 + 强一致性 悲观锁 synchronized/ReentrantLock/数据库行锁
核心交易/资金场景 悲观锁 数据库行锁 + 事务
非核心数据/统计场景 乐观锁 CAS 原子操作
高并发读 + 偶尔写 乐观锁 版本号机制 + 重试逻辑
低并发写 + 数据安全优先 悲观锁 synchronized 关键字

五、面试高频回答模板(精简版)

选择乐观锁或悲观锁,核心看业务场景的读写比例数据一致性要求

  1. 若场景是读多写少、弱一致性要求(如商品详情、积分统计),优先选乐观锁,无锁竞争能提升读性能,少量写冲突可通过重试解决;
  2. 若场景是写多读少、强一致性要求(如库存扣减、转账),优先选悲观锁,通过加锁保证数据安全,避免并发写导致的数据错误;
  3. 技术层面补充:乐观锁需处理 ABA 问题和重试逻辑,悲观锁需注意死锁和锁粒度问题。
相关推荐
ADHD多动联盟2 小时前
如何通过运动干预方案提升孩子的社交技能与学习能力?
学习·学习方法·玩游戏
东离与糖宝2 小时前
AI 智能体安全踩坑记:Java 为 OpenClaw 添加权限控制与审计日志实战
java·人工智能
somi72 小时前
51单片机-01-基础概念
单片机·嵌入式硬件·学习·51单片机
晓晓hh2 小时前
JavaSe学习——基础
java·开发语言·学习
峥嵘life2 小时前
Android16 【CTS】CtsWindowManagerDeviceAnimations存在fail项
android·linux·学习
phltxy2 小时前
算法刷题|模拟思想高频题全解(Java版)
java·开发语言·算法
wuyikeer2 小时前
Java进阶(ElasticSearch的安装与使用)
java·elasticsearch·jenkins
C雨后彩虹2 小时前
深入探索Java Stream:6个复杂业务场景下的高效实现方案
java·多线程·stream·同步·异步
愚者游世2 小时前
template学习大纲
开发语言·c++·程序人生·面试·visual studio