Interview AiBoxInterview AiBox 实时 AI 助手,让你自信应答每一场面试
请解释乐观锁和悲观锁的概念、实现方式及适用场景。
题型摘要
乐观锁和悲观锁是两种常见的并发控制策略。悲观锁假设冲突会发生,在操作数据前先获取锁,确保数据一致性,适合写多读少、冲突可能性高的场景,但可能降低并发性能。乐观锁假设冲突不会发生,在更新数据时检查是否被修改,适合读多写少、冲突可能性低的场景,能提高并发性能但实现较复杂。悲观锁通过数据库锁、编程语言锁等实现,乐观锁通过版本号、时间戳、CAS操作等实现。选择哪种锁策略取决于具体场景的读写比例、冲突可能性和性能要求。
乐观锁和悲观锁的概念、实现方式及适用场景
概念解释
悲观锁: 悲观锁是一种悲观的并发控制策略,它假设在数据处理过程中,很可能会发生冲突,因此在数据处理前就先获取锁,确保在整个数据处理过程中,数据不会被其他事务修改。只有当前事务完成后,才会释放锁,其他事务才能获取该数据。
乐观锁: 乐观锁是一种乐观的并发控制策略,它假设在数据处理过程中,不太可能发生冲突,因此在数据处理前不获取锁。而是在更新数据时,检查数据是否被其他事务修改过。如果数据未被修改,则更新成功;如果数据已被修改,则更新失败,需要进行重试或其他处理。
实现方式
悲观锁的实现方式
-
数据库锁:
- 行锁:例如MySQL的
SELECT ... FOR UPDATE语句 - 表锁:例如MySQL的
LOCK TABLES语句 - 页锁:例如InnoDB的页面级锁
- 行锁:例如MySQL的
-
编程语言中的锁:
- Java中的
synchronized关键字 - Java中的
ReentrantLock类 - Python中的
threading.Lock类 - C#中的
lock关键字
- Java中的
-
分布式锁:
- 基于Redis的分布式锁(如RedLock算法)
- 基于ZooKeeper的分布式锁
- 基于数据库的分布式锁
乐观锁的实现方式
-
版本号机制:
- 在数据表中添加一个版本号字段(version)
- 更新数据时检查版本号是否发生变化
- 如果版本号未变,则更新数据并增加版本号
- 如果版本号已变,则更新失败
-
时间戳机制:
- 在数据表中添加一个时间戳字段(timestamp)
- 更新数据时检查时间戳是否发生变化
- 如果时间戳未变,则更新数据并更新时间戳
- 如果时间戳已变,则更新失败
-
CAS(Compare And Swap)操作:
- 原子操作,比较内存中的值与预期值是否相等
- 如果相等,则将新值写入内存
- 如果不相等,则操作失败
- Java中的
AtomicInteger、AtomicLong等类使用了CAS操作
-
MD5校验和:
- 在读取数据时计算数据的MD5值
- 更新数据时重新计算MD5值并与之前的比较
- 如果MD5值相同,则更新数据
- 如果MD5值不同,则更新失败
适用场景
悲观锁的适用场景
- 写操作频繁:当对数据的写操作远多于读操作时,使用悲观锁可以避免大量的更新冲突。
- 冲突可能性高:当多个事务同时修改同一数据的可能性很高时,使用悲观锁可以确保数据的一致性。
- 数据一致性要求高:当对数据的一致性要求非常高,不能接受任何数据冲突时,使用悲观锁可以确保数据的一致性。
- 事务执行时间长:当一个事务执行时间较长,期间数据被其他事务修改的可能性较高时,使用悲观锁可以确保数据的一致性。
乐观锁的适用场景
- 读操作频繁:当对数据的读操作远多于写操作时,使用乐观锁可以提高系统的并发性能。
- 冲突可能性低:当多个事务同时修改同一数据的可能性很低时,使用乐观锁可以提高系统的并发性能。
- 系统性能要求高:当对系统的性能要求非常高,需要尽可能减少锁的开销时,使用乐观锁可以提高系统的性能。
- 事务执行时间短:当一个事务执行时间很短,期间数据被其他事务修改的可能性较低时,使用乐观锁可以提高系统的并发性能。
优缺点对比
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 实现复杂度 | 较简单 | 较复杂 |
| 并发性能 | 较低 | 较高 |
| 死锁风险 | 有 | 无 |
| 冲突处理 | 阻塞等待 | 重试或放弃 |
| 适用场景 | 写多读少,冲突可能性高 | 读多写少,冲突可能性低 |
| 资源开销 | 较高(需要维护锁) | 较低(不需要维护锁) |
| 实现方式 | 数据库锁、编程语言锁、分布式锁 | 版本号、时间戳、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;
可视化图表
悲观锁工作流程图
乐观锁工作流程图
乐观锁与悲观锁对比图
乐观锁CAS操作时序图
思维导图
Interview AiBoxInterview AiBox — 面试搭档
不只是准备,更是实时陪练
Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。
AI 助读
一键发送到常用 AI
乐观锁和悲观锁是两种常见的并发控制策略。悲观锁假设冲突会发生,在操作数据前先获取锁,确保数据一致性,适合写多读少、冲突可能性高的场景,但可能降低并发性能。乐观锁假设冲突不会发生,在更新数据时检查是否被修改,适合读多写少、冲突可能性低的场景,能提高并发性能但实现较复杂。悲观锁通过数据库锁、编程语言锁等实现,乐观锁通过版本号、时间戳、CAS操作等实现。选择哪种锁策略取决于具体场景的读写比例、冲突可能性和性能要求。
智能总结
深度解读
考点定位
思路启发
相关题目
请做一个自我介绍
自我介绍是面试的开场环节,应遵循"三段式"结构:基本信息与教育背景、核心能力与项目经验、求职动机与个人特质。重点突出与岗位相关的技能和经验,用具体数据和成果支撑,保持真诚自然的表达,控制在2-3分钟内。针对不同公司和岗位进行个性化调整,展示自己的匹配度和价值。
你有什么问题想问我们公司或团队的吗?
面试结尾提问是展示面试者思考深度和职业素养的重要机会。应提前准备3-5个有深度的问题,围绕团队技术、个人成长、公司文化和业务发展四个方面。好的问题能体现你对公司的了解、对职位的重视以及你的职业规划,避免问基础信息类问题。
请做一个自我介绍
自我介绍应遵循“我是谁-我为什么能胜任-我为什么想来”的逻辑框架。在“能胜任”部分,要通过STAR法则和量化结果来突出技术亮点和项目经验。在“想来”部分,要表达对华为技术、文化或业务的认同,展现匹配度和诚意。整个过程应简洁有力,控制在1-3分钟内。
请做一个自我介绍
自我介绍是面试的开场环节,应简洁明了地展示个人基本信息、教育背景、项目经验、技术特长、个人特质和求职动机。优秀的自我介绍应结构清晰、重点突出,与应聘岗位高度匹配,并表达出对公司的了解和加入的强烈意愿。
请做一个自我介绍,包括你的技术背景、项目经验和学习方向。
自我介绍应包含四个核心部分:个人背景、技术能力、项目经验和学习规划。技术背景需突出前端技术栈掌握程度;项目经验应选择代表性案例,说明技术实现和个人贡献;学习方向要体现职业规划与公司发展的契合度。整体表达应简洁有力,重点突出,时间控制在3-5分钟内。