网站首页 > 教程文章 正文
乐观锁与悲观锁的对比及实现方式
一、核心概念对比
维度 | 悲观锁 | 乐观锁 |
设计理念 | 假设并发冲突必然发生,预先加锁防止数据修改 | 假设并发冲突较少发生,提交时检测冲突 |
适用场景 | 写操作频繁,数据竞争激烈 | 读多写少,冲突概率低 |
性能开销 | 加锁/释放锁带来较大开销,高并发下可能成为瓶颈 | 无锁设计,冲突检测时可能有重试开销 |
数据一致性 | 强一致性 | 最终一致性 |
二、实现方式详解
1. 悲观锁实现
Java 原生支持:
- synchronized 关键字
public synchronized void updateData() { // 临界区操作(自动加锁/解锁) }
- ReentrantLock
ReentrantLock lock = new ReentrantLock();
public void updateData() { lock.lock();
try { // 临界区操作
} finally {
lock.unlock();
} }
数据库实现:
- SELECT ... FOR UPDATE(行级锁)
BEGIN;
SELECT * FROM account WHERE id = 1 FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;
2. 乐观锁实现
Java CAS 操作:
- Atomic 原子类
AtomicInteger count = new AtomicInteger(0);
public void safeIncrement() {
int oldValue, newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue));
}
数据库版本号机制:
- Version 字段控制
UPDATE product
SET stock = stock - 1,
version = version + 1
WHERE id = 100 AND version = 2; -- 检查影响行数,若为0则重试
框架支持:
- JPA/Hibernate
@Entity public class Product {
@Id private Long id;
private int stock;
@Version private int version; // 自动管理版本号
}
三、性能对比与选择策略
场景 | 推荐锁类型 | 理由 |
短事务高频写 | 悲观锁 | 避免频繁冲突导致的重试开销(如秒杀库存扣减) |
长事务复杂操作 | 乐观锁 | 减少锁持有时间,防止长事务阻塞(如订单状态流转) |
读多写少 | 乐观锁 | 无锁设计提升吞吐量(如商品详情浏览) |
分布式系统 | 乐观锁 | 避免跨服务锁管理的复杂性(如基于版本号的分布式事务) |
四、实现方案最佳实践
1. 高并发秒杀(悲观锁)
java
public boolean seckill(Long productId) {
// 使用分布式锁(如Redisson)
RLock lock = redisson.getLock("seckill:" + productId);
try {
lock.lock(3, TimeUnit.SECONDS); // 控制锁超时时间
Product product = productDao.selectForUpdate(productId);
if (product.getStock() > 0) {
productDao.updateStock(productId);
return true;
}
return false;
} finally {
lock.unlock();
}
}
2. 购物车商品数量修改(乐观锁)
java
public boolean updateCartQuantity(Long cartId, int newQty) {
Cart cart = cartDao.get(cartId);
int retry = 0;
while (retry < 3) { // 最大重试次数
int oldVersion = cart.getVersion();
cart.setQuantity(newQty);
int rows = cartDao.updateWithVersion(
cartId, newQty, oldVersion, oldVersion + 1
);
if (rows > 0) return true;
cart = cartDao.get(cartId); // 重新加载最新数据
retry++;
}
return false;
}
五、常见面试追问及应答
Q1:CAS操作有什么缺点?
- ABA问题:通过版本号或时间戳解决(如AtomicStampedReference)
- 自旋开销:冲突激烈时CPU占用高,需配合退避策略(如指数退避)
Q2:如何选择数据库乐观锁的实现方式?
- 版本号字段:需额外维护字段,适合业务数据模型
- 时间戳:精度可能不足,存在并发风险
- 条件更新:直接在WHERE子句中校验原值(如WHERE stock = oldStock)
Q3:分布式场景下如何实现乐观锁?
- Redis WATCH/MULTI:监控Key变化后执行事务
- ZooKeeper版本号:znode的dataVersion属性控制
- 分布式事务框架:Seata的AT模式通过全局锁实现
总结
- 悲观锁:通过独占资源确保强一致性,适合写冲突频繁场景,但需注意死锁风险和性能损耗。
- 乐观锁:通过版本控制实现无锁并发,适合读多写少场景,需处理冲突重试和ABA问题。
技术选型建议:
- 优先评估业务场景的读写比例和冲突概率
- 简单场景用语言级锁(如synchronized),复杂场景用框架支持(如JPA乐观锁)
- 分布式系统需结合中间件(如Redis/ZooKeeper)实现跨服务锁管理
猜你喜欢
- 2025-07-28 Spring框架基础知识-第三节内容(spring框架的两种使用方式)
- 2025-07-28 SpringBoot中使用Spring Data JPA
- 2025-07-28 12《Spring Boot 入门教程》Spring Boot 使用 JPA
- 2025-07-28 Spring Data JPA系列2:SpringBoot集成JPA详细教程,快速上手JPA
- 2025-07-28 Java中PO、BO、VO、DTO、POJO、DAO概念及其作用
- 2025-07-28 springboot面试题(springcloud面试题)
- 2025-07-28 Spring 事务管理详情介绍(spring的事务管理是如何实现的)
- 2025-07-28 说一说 JPA + Oracle 项目启动时遇到一大坑,你碰到过吗?
- 2025-07-28 Spring Boot 最佳实践(五)Spring Data JPA 操作 MySQL 8
- 2025-07-28 JAP简易教程之概述.md(japone java)
- 最近发表
- 标签列表
-
- location.href (44)
- document.ready (36)
- git checkout -b (34)
- 跃点数 (35)
- 阿里云镜像地址 (33)
- qt qmessagebox (36)
- mybatis plus page (35)
- vue @scroll (38)
- 堆栈区别 (33)
- 什么是容器 (33)
- sha1 md5 (33)
- navicat导出数据 (34)
- 阿里云acp考试 (33)
- 阿里云 nacos (34)
- redhat官网下载镜像 (36)
- srs服务器 (33)
- pico开发者 (33)
- https的端口号 (34)
- vscode更改主题 (35)
- 阿里云资源池 (34)
- os.path.join (33)
- redis aof rdb 区别 (33)
- 302跳转 (33)
- http method (35)
- js array splice (33)