乐观锁与悲观锁:并发控制的两种核心策略
在多用户并发访问共享资源的场景中,数据一致性问题始终是关键挑战。乐观锁与悲观锁作为两种截然不同的并发控制策略,在数据库和编程领域被广泛应用。下面将从概念、实现原理、适用场景等多个维度深入解析这两种锁机制。
一、悲观锁:保守的并发控制思想
核心概念
悲观锁基于"最坏情况"假设,认为在数据处理过程中必然会出现并发冲突,因此在操作数据前就对其加锁,确保同一时间只有一个线程能访问该数据。
实现方式
数据库层面
共享锁(读锁/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();
}
}
典型场景
金融交易系统:资金转账时需确保账户余额操作的原子性。
票务系统:库存扣减时避免超卖。
二、乐观锁:乐观的并发控制思想
核心概念
乐观锁假设并发冲突发生的概率较低,不主动加锁,而是在更新数据时检查是否有其他线程修改过该数据。若数据未被修改,则执行更新;若已被修改,则放弃或重试。
实现方式
版本号(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行,需前端重试。
六、总结:如何选择合适的锁策略?
优先选乐观锁:当系统读操作远多于写操作,且允许少量更新失败时(如社交、资讯类应用)。
优先选悲观锁:当数据一致性要求极高,且写操作频繁时(如金融、票务系统)。
动态调整:部分系统会根据运行时的冲突概率动态切换策略,如高并发时段自动启用悲观锁。
理解这两种锁机制的本质,能帮助开发者在分布式系统设计中更精准地平衡性能与一致性,避免因并发控制不当导致的数据异常问题。