乐观锁与悲观锁
约 919 字大约 3 分钟
2025-08-26
定义
- 乐观锁 (Optimistic Locking):持有乐观态度,假设并发冲突的概率较低。在数据提交时检查数据是否被其他事务修改,如果未被修改则提交更新;否则,进行重试或抛出异常。核心思想是减少锁的开销,依赖冲突检测机制。
- 悲观锁 (Pessimistic Locking):持有悲观态度,假设并发冲突的概率较高。在每次操作数据前直接加锁(如行锁或表锁),确保线程安全,避免并发修改。核心思想是预防冲突,通过串行化操作保证数据一致性。
| 方面 | 乐观锁 | 悲观锁 |
|---|---|---|
| 并发态度 | 假设冲突概率低,延迟冲突检测 | 假设冲突概率高,提前加锁预防 |
| 实现机制 | 通过版本号或 CAS 等无锁机制实现 | 通过锁机制(如 synchronized 或数据库锁)实现 |
| 性能开销 | 较低(无锁操作,减少阻塞) | 较高(加锁操作可能导致线程阻塞) |
| 适用场景 | 读多写少(冲突概率低) | 写多读少(冲突概率高) |
| 数据一致性 | 可能因冲突导致失败(需重试或异常处理) | 强一致性(通过锁保证操作原子性) |
| 并发性能 | 高(支持高并发,减少资源争用) | 低(串行化操作,可能成为瓶颈) |
| 失败处理 | 需处理重试或异常(如版本不匹配) | 无需额外处理(锁确保操作成功) |
应用场景
- 乐观锁适用场景:
- 读多写少的系统(如缓存更新、配置管理),冲突概率低,可降低锁带来的性能开销。
- 系统能容忍或优雅处理失败(如重试机制),避免因冲突导致的数据回滚成本高。
- 高并发环境(如互联网应用),要求高性能和低延迟。
- 悲观锁适用场景:
- 写多读少的系统(如金融交易、库存扣减),冲突概率高,避免频繁重试带来的性能损耗。
- 数据一致性要求极高(如银行转账),需确保操作原子性和强一致性。
- 低并发环境,或对性能要求不敏感的场景。
如何实现乐观锁
乐观锁的实现主要依赖无锁机制,常见方法包括版本号机制和 CAS 操作。
版本号机制
为数据表添加一个版本号字段(如 version)。在更新数据时,先查询当前版本号,然后执行更新操作,并检查版本号是否匹配。如果匹配,则更新数据并递增版本号;否则,表示数据已被修改,操作失败需重试或异常处理。
SQL 示例:
-- 步骤 1: 查询目标记录的当前值和版本号
SELECT value, version FROM table WHERE id = 1;
-- 步骤 2: 执行更新,检查版本号是否一致
-- 若一致,则更新值并递增版本号;否则,更新失败(影响行数为 0)
UPDATE table
SET value = new_value, version = version + 1
WHERE id = 1 AND version = current_version;CAS 机制
CAS (Compare and Swap) 是一种原子操作,通过比较内存值与预期值是否相等来决定是否更新。如果相等,则更新为新值;否则,操作失败。常见于编程语言中的原子类(如 Java 的 AtomicInteger)。
Java 示例:
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger value = new AtomicInteger(0);
public void updateValue(int expectedValue, int newValue) {
// CAS 操作:比较当前值是否等于 expectedValue,是则更新为 newValue
boolean success = value.compareAndSet(expectedValue, newValue);
if (!success) {
// 处理冲突:重试或抛出异常
throw new IllegalStateException("Concurrent modification detected");
}
}
}