Interview AiBox logo

Interview AiBox 实时 AI 助手,让你自信应答每一场面试

download免费下载
3local_fire_department54 次面试更新于 2025-08-23account_tree思维导图

请解释乐观锁和悲观锁的概念、实现方式及适用场景。

lightbulb

题型摘要

乐观锁和悲观锁是两种常见的并发控制策略。悲观锁假设冲突会发生,在操作数据前先获取锁,确保数据一致性,适合写多读少、冲突可能性高的场景,但可能降低并发性能。乐观锁假设冲突不会发生,在更新数据时检查是否被修改,适合读多写少、冲突可能性低的场景,能提高并发性能但实现较复杂。悲观锁通过数据库锁、编程语言锁等实现,乐观锁通过版本号、时间戳、CAS操作等实现。选择哪种锁策略取决于具体场景的读写比例、冲突可能性和性能要求。

乐观锁和悲观锁的概念、实现方式及适用场景

概念解释

悲观锁: 悲观锁是一种悲观的并发控制策略,它假设在数据处理过程中,很可能会发生冲突,因此在数据处理前就先获取锁,确保在整个数据处理过程中,数据不会被其他事务修改。只有当前事务完成后,才会释放锁,其他事务才能获取该数据。

乐观锁: 乐观锁是一种乐观的并发控制策略,它假设在数据处理过程中,不太可能发生冲突,因此在数据处理前不获取锁。而是在更新数据时,检查数据是否被其他事务修改过。如果数据未被修改,则更新成功;如果数据已被修改,则更新失败,需要进行重试或其他处理。

实现方式

悲观锁的实现方式

  1. 数据库锁

    • 行锁:例如MySQL的SELECT ... FOR UPDATE语句
    • 表锁:例如MySQL的LOCK TABLES语句
    • 页锁:例如InnoDB的页面级锁
  2. 编程语言中的锁

    • Java中的synchronized关键字
    • Java中的ReentrantLock
    • Python中的threading.Lock
    • C#中的lock关键字
  3. 分布式锁

    • 基于Redis的分布式锁(如RedLock算法)
    • 基于ZooKeeper的分布式锁
    • 基于数据库的分布式锁

乐观锁的实现方式

  1. 版本号机制

    • 在数据表中添加一个版本号字段(version)
    • 更新数据时检查版本号是否发生变化
    • 如果版本号未变,则更新数据并增加版本号
    • 如果版本号已变,则更新失败
  2. 时间戳机制

    • 在数据表中添加一个时间戳字段(timestamp)
    • 更新数据时检查时间戳是否发生变化
    • 如果时间戳未变,则更新数据并更新时间戳
    • 如果时间戳已变,则更新失败
  3. CAS(Compare And Swap)操作

    • 原子操作,比较内存中的值与预期值是否相等
    • 如果相等,则将新值写入内存
    • 如果不相等,则操作失败
    • Java中的AtomicIntegerAtomicLong等类使用了CAS操作
  4. MD5校验和

    • 在读取数据时计算数据的MD5值
    • 更新数据时重新计算MD5值并与之前的比较
    • 如果MD5值相同,则更新数据
    • 如果MD5值不同,则更新失败

适用场景

悲观锁的适用场景

  1. 写操作频繁:当对数据的写操作远多于读操作时,使用悲观锁可以避免大量的更新冲突。
  2. 冲突可能性高:当多个事务同时修改同一数据的可能性很高时,使用悲观锁可以确保数据的一致性。
  3. 数据一致性要求高:当对数据的一致性要求非常高,不能接受任何数据冲突时,使用悲观锁可以确保数据的一致性。
  4. 事务执行时间长:当一个事务执行时间较长,期间数据被其他事务修改的可能性较高时,使用悲观锁可以确保数据的一致性。

乐观锁的适用场景

  1. 读操作频繁:当对数据的读操作远多于写操作时,使用乐观锁可以提高系统的并发性能。
  2. 冲突可能性低:当多个事务同时修改同一数据的可能性很低时,使用乐观锁可以提高系统的并发性能。
  3. 系统性能要求高:当对系统的性能要求非常高,需要尽可能减少锁的开销时,使用乐观锁可以提高系统的性能。
  4. 事务执行时间短:当一个事务执行时间很短,期间数据被其他事务修改的可能性较低时,使用乐观锁可以提高系统的并发性能。

优缺点对比

特性 悲观锁 乐观锁
实现复杂度 较简单 较复杂
并发性能 较低 较高
死锁风险
冲突处理 阻塞等待 重试或放弃
适用场景 写多读少,冲突可能性高 读多写少,冲突可能性低
资源开销 较高(需要维护锁) 较低(不需要维护锁)
实现方式 数据库锁、编程语言锁、分布式锁 版本号、时间戳、CAS、MD5校验和

代码示例

悲观锁示例(Java)

// 使用synchronized关键字实现悲观锁
public class PessimisticLockExample {
    private int count = 0;
    
    // synchronized关键字确保同一时间只有一个线程可以执行该方法
    public synchronized void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

// 使用ReentrantLock实现悲观锁
import java.util.concurrent.locks.ReentrantLock;

public class PessimisticLockExample2 {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
    
    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;
        } finally {
            lock.unlock();  // 释放锁
        }
    }
    
    public int getCount() {
        return count;
    }
}

乐观锁示例(Java)

// 使用版本号实现乐观锁
public class OptimisticLockExample {
    private int count = 0;
    private int version = 0;
    
    public boolean increment() {
        int currentVersion = version;
        // 模拟一些处理时间
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 检查版本号是否变化
        if (currentVersion == version) {
            count++;
            version++;
            return true;  // 更新成功
        } else {
            return false;  // 更新失败
        }
    }
    
    public int getCount() {
        return count;
    }
    
    public int getVersion() {
        return version;
    }
}

// 使用AtomicInteger实现乐观锁(基于CAS)
import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample2 {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        // 使用CAS操作实现原子递增
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
}

数据库中的悲观锁和乐观锁示例

-- 悲观锁示例(MySQL)
-- 开始事务
START TRANSACTION;

-- 使用SELECT ... FOR UPDATE获取行锁
SELECT * FROM products WHERE id = 1 FOR UPDATE;

-- 更新数据
UPDATE products SET stock = stock - 1 WHERE id = 1;

-- 提交事务,释放锁
COMMIT;

-- 乐观锁示例(MySQL)
-- 假设products表有一个version字段
-- 开始事务
START TRANSACTION;

-- 读取数据和版本号
SELECT id, name, stock, version FROM products WHERE id = 1;

-- 更新数据时检查版本号
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = [之前读取的版本号];

-- 检查是否更新成功
-- 如果受影响的行数为0,说明版本号已变化,更新失败
-- 提交事务
COMMIT;

可视化图表

悲观锁工作流程图

--- title: 悲观锁工作流程 --- flowchart TD A["事务开始"] --> B["获取锁"] B --> C["锁是否可用?"] C -->|是| D["获取锁成功"] C -->|否| E["等待锁释放"] E --> C D --> F["读取/修改数据"] F --> G["提交事务"] G --> H["释放锁"] H --> I["事务结束"]

乐观锁工作流程图

--- title: 乐观锁工作流程 --- flowchart TD A["事务开始"] --> B["读取数据"] B --> C["记录版本号/时间戳"] C --> D["修改数据"] D --> E["提交事务"] E --> F["检查版本号/时间戳是否变化"] F -->|未变化| G["更新数据并增加版本号/更新时间戳"] F -->|已变化| H["更新失败,回滚或重试"] G --> I["事务结束"] H --> J["是否重试?"] J -->|是| B J -->|否| I

乐观锁与悲观锁对比图

--- title: 乐观锁与悲观锁对比 --- graph LR subgraph 悲观锁 P1["假设冲突会发生"] P2["先获取锁,再操作数据"] P3["适合写多读少场景"] P4["可能导致死锁"] P5["实现相对简单"] end subgraph 乐观锁 O1["假设冲突不会发生"] O2["先操作数据,更新时检查冲突"] O3["适合读多写少场景"] O4["不会导致死锁"] O5["实现相对复杂"] end P1 -.-> O1 P2 -.-> O2 P3 -.-> O3 P4 -.-> O4 P5 -.-> O5

乐观锁CAS操作时序图

--- title: 乐观锁CAS操作时序图 --- sequenceDiagram participant T1 as 线程1 participant T2 as 线程2 participant M as 内存 T1->>M: 读取值V M-->>T1: 返回值V T1->>T1: 计算新值V' T2->>M: 读取值V M-->>T2: 返回值V T2->>T2: 计算新值V'' T2->>M: CAS(V, V'') M-->>T2: CAS成功 Note over M: 内存中的值更新为V'' T1->>M: CAS(V, V') M-->>T1: CAS失败 Note over T1: 值已变化,需要重试或放弃
account_tree

思维导图

Interview AiBox logo

Interview AiBox — 面试搭档

不只是准备,更是实时陪练

Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。

AI 助读

一键发送到常用 AI

乐观锁和悲观锁是两种常见的并发控制策略。悲观锁假设冲突会发生,在操作数据前先获取锁,确保数据一致性,适合写多读少、冲突可能性高的场景,但可能降低并发性能。乐观锁假设冲突不会发生,在更新数据时检查是否被修改,适合读多写少、冲突可能性低的场景,能提高并发性能但实现较复杂。悲观锁通过数据库锁、编程语言锁等实现,乐观锁通过版本号、时间戳、CAS操作等实现。选择哪种锁策略取决于具体场景的读写比例、冲突可能性和性能要求。

智能总结

深度解读

考点定位

思路启发

auto_awesome

相关题目

请做一个自我介绍

自我介绍是面试的开场环节,应遵循"三段式"结构:基本信息与教育背景、核心能力与项目经验、求职动机与个人特质。重点突出与岗位相关的技能和经验,用具体数据和成果支撑,保持真诚自然的表达,控制在2-3分钟内。针对不同公司和岗位进行个性化调整,展示自己的匹配度和价值。

arrow_forward

你有什么问题想问我们公司或团队的吗?

面试结尾提问是展示面试者思考深度和职业素养的重要机会。应提前准备3-5个有深度的问题,围绕团队技术、个人成长、公司文化和业务发展四个方面。好的问题能体现你对公司的了解、对职位的重视以及你的职业规划,避免问基础信息类问题。

arrow_forward

请做一个自我介绍

自我介绍应遵循“我是谁-我为什么能胜任-我为什么想来”的逻辑框架。在“能胜任”部分,要通过STAR法则和量化结果来突出技术亮点和项目经验。在“想来”部分,要表达对华为技术、文化或业务的认同,展现匹配度和诚意。整个过程应简洁有力,控制在1-3分钟内。

arrow_forward

请做一个自我介绍

自我介绍是面试的开场环节,应简洁明了地展示个人基本信息、教育背景、项目经验、技术特长、个人特质和求职动机。优秀的自我介绍应结构清晰、重点突出,与应聘岗位高度匹配,并表达出对公司的了解和加入的强烈意愿。

arrow_forward

请做一个自我介绍,包括你的技术背景、项目经验和学习方向。

自我介绍应包含四个核心部分:个人背景、技术能力、项目经验和学习规划。技术背景需突出前端技术栈掌握程度;项目经验应选择代表性案例,说明技术实现和个人贡献;学习方向要体现职业规划与公司发展的契合度。整体表达应简洁有力,重点突出,时间控制在3-5分钟内。

arrow_forward

阅读状态

阅读时长

8 分钟

阅读进度

6%

章节:17 · 已读:1

当前章节: 概念解释

最近更新:2025-08-23

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

面试中屏幕实时显示参考回答,帮你打磨表达。

免费下载download

分享题目

复制链接,或一键分享到常用平台

外部分享