Interview AiBox logo

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

download免费下载
进阶local_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

相关题目

数组和链表在数据结构上有什么区别?

数组和链表是两种基础线性数据结构,主要区别在于内存存储方式(连续vs非连续)和操作效率。数组支持O(1)随机访问但插入删除需O(n),链表插入删除高效(O(1))但随机访问需O(n)。数组适合静态数据、频繁访问场景;链表适合动态数据、频繁增删场景。选择时应根据具体需求权衡。

arrow_forward

请做一个自我介绍

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

arrow_forward

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

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

arrow_forward

请解释TCP三次握手的过程。

TCP三次握手是建立可靠网络连接的关键过程,通过SYN、SYN+ACK和ACK三个数据包的交换,确保客户端和服务端都具备收发能力并同步序列号。第一次握手客户端发送SYN包并进入SYN_SENT状态;第二次握手服务端回复SYN+ACK包并进入SYN_RCVD状态;第三次握手客户端发送ACK包,双方都进入ESTABLISHED状态,连接建立完成。三次握手而非两次或四次的设计是为了在保证可靠性的同时避免不必要的延迟和潜在问题。

arrow_forward

请介绍一下你的实习项目经历

这道题考察面试者的项目经验总结、技术表达、问题解决和自我反思能力。回答应包括项目概述、技术栈、项目职责、具体工作、技术难点与解决方案、项目成果以及收获与反思。示例答案展示了一个在滴滴实习的前端开发应届生如何结构化地介绍自己参与的H5页面重构项目,包括使用React+TypeScript技术栈、负责订单流程页面重构、组件库开发、性能优化等工作,以及解决复杂表单状态管理和移动端适配等技术难点,最终实现了性能提升和用户体验改善的成果。

arrow_forward

阅读状态

阅读时长

8 分钟

阅读进度

6%

章节:17 · 已读:1

当前章节: 概念解释

最近更新:2025-08-23

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

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

免费下载download

分享题目

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

外部分享