乐观锁与悲观锁
乐观锁与悲观锁:并发控制的两种核心策略
在多用户并发访问共享资源的场景中,数据一致性问题始终是关键挑战。乐观锁与悲观锁作为两种截然不同的并发控制策略,在数据库和编程领域被广泛应用。下面将从概念、实现原理、适用场景等多个维度深入解析这两种锁机制。
一、悲观锁:保守的并发控制思想
核心概念
悲观锁基于"最坏情况"假设,认为在数据处理过程中必然会出现并发冲突,因此在操作数据前就对其加锁,确保同一时间只有一个线程能访问该数据。
实现方式
-
数据库层面
- 共享锁(读锁/S锁):允许多个线程同时读取数据,但阻止其他线程获取写锁。
- 排他锁(写锁/X锁):仅允许一个线程获取,获取后其他线程无法读取或修改数据。
- 示例(MySQL):
-- 对表加排他锁 SELECT * FROM table_name WHERE id=1 FOR UPDATE;
-
编程层面
- 使用编程语言提供的锁机制,如Java中的
synchronized关键字、ReentrantLock等。
private final ReentrantLock lock = new ReentrantLock(); public void updateData() { lock.lock(); try { // 数据操作 } finally { lock.unlock(); } } - 使用编程语言提供的锁机制,如Java中的
典型场景
- 金融交易系统:资金转账时需确保账户余额操作的原子性。
- 票务系统:库存扣减时避免超卖。
二、乐观锁:乐观的并发控制思想
核心概念
乐观锁假设并发冲突发生的概率较低,不主动加锁,而是在更新数据时检查是否有其他线程修改过该数据。若数据未被修改,则执行更新;若已被修改,则放弃或重试。
实现方式
-
版本号(Version)机制
- 表中添加
version字段,每次更新时版本号+1。 - 更新语句示例:
UPDATE table_name SET data=?, version=version+1 WHERE id=? AND version=?; - 若更新行数为0,说明数据已被修改,需重新获取数据并重试。
- 表中添加
-
时间戳(Timestamp)机制
- 使用数据最后修改的时间戳作为冲突判断依据,原理与版本号类似。
-
CAS(Compare-And-Swap)算法
- 编程层面的无锁实现,核心逻辑:
// 假设初始值为expectedValue,尝试更新为newValue boolean result = atomicVar.compareAndSet(expectedValue, newValue); - 典型应用:Java的
AtomicInteger、ConcurrentHashMap等并发工具类。
- 编程层面的无锁实现,核心逻辑:
典型场景
- 社交平台动态点赞:冲突概率低,无需加锁影响用户体验。
- 电商商品浏览统计:读多写少场景,允许少量更新冲突。
三、乐观锁与悲观锁的核心对比
| 对比维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 并发策略 | 先加锁再操作,阻塞其他线程 | 无锁操作,更新时检查冲突 |
| 性能消耗 | 锁竞争和线程阻塞带来额外开销 | 无锁开销,但可能需要多次重试 |
| 适用场景 | 写操作频繁、冲突概率高的场景 | 读操作频繁、冲突概率低的场景 |
| 实现复杂度 | 数据库原生支持,实现简单 | 需要额外字段(版本号)或算法支持 |
| 死锁风险 | 存在死锁可能(如多锁交叉持有) | 无死锁风险 |
| 一致性保证 | 强一致性(加锁期间数据不可变) | 最终一致性(允许更新失败后重试) |
四、高级应用与优化策略
-
乐观锁的重试机制
- 固定次数重试:适用于冲突概率稳定的场景。
- 指数退避重试:冲突时等待时间递增(如100ms→200ms→400ms),减少CPU消耗。
-
悲观锁的优化
- 缩小锁范围:仅对关键代码块加锁,而非整个方法。
- 使用分段锁:如Java的
ConcurrentHashMap将数据分段加锁,提高并发度。
-
混合策略
- 在读多写少场景中,可结合乐观锁与悲观锁:读操作使用乐观锁,写操作使用悲观锁。
五、实际案例:电商库存扣减的两种实现
-
悲观锁实现
-- 扣减库存(加排他锁) BEGIN TRANSACTION; SELECT stock FROM products WHERE id=1 FOR UPDATE; -- 假设当前库存为10 UPDATE products SET stock=9 WHERE id=1; COMMIT; -
乐观锁实现
-- 假设表中有version字段,当前值为5 UPDATE products SET stock=stock-1, version=version+1 WHERE id=1 AND stock>0 AND version=5;- 若更新成功,返回1行;若库存不足或版本号不一致,返回0行,需前端重试。
六、总结:如何选择合适的锁策略?
- 优先选乐观锁:当系统读操作远多于写操作,且允许少量更新失败时(如社交、资讯类应用)。
- 优先选悲观锁:当数据一致性要求极高,且写操作频繁时(如金融、票务系统)。
- 动态调整:部分系统会根据运行时的冲突概率动态切换策略,如高并发时段自动启用悲观锁。
理解这两种锁机制的本质,能帮助开发者在分布式系统设计中更精准地平衡性能与一致性,避免因并发控制不当导致的数据异常问题。

浙公网安备 33010602011771号