乐观锁(Optimistic Locking)是一种轻量级的并发控制机制,假设数据在大多数情况下不会被其他线程修改,因此不直接加锁,而是在更新时检查数据是否被篡改。相比传统的悲观锁(如synchronized),乐观锁避免了线程阻塞,提高了并发性能。
核心原理
无锁操作
线程在读取数据时不加锁,直接进行业务操作,仅在更新数据时检查是否有其他线程修改过。版本控制
常见实现方式是为数据添加版本号(Version)或时间戳(Timestamp):- 读取数据时记录版本号。
- 更新时比较版本号,若一致则执行更新并递增版本号;否则失败重试。
乐观锁的优缺点
优点
- 高并发性能:无锁竞争,避免线程上下文切换。
- 适用于读多写少场景:如缓存更新、报表统计等。
缺点
- 写冲突重试成本高:频繁写操作可能导致大量重试。
- 不适合长事务:长时间持有数据可能增加冲突概率。
实现方式
1. CAS(Compare-and-Swap)
- 原理:原子操作,比较内存中的值与预期值,若相同则更新为新值。
- Java实现:
AtomicInteger、AtomicReference等原子类。
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger value = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = value.get();
newValue = oldValue + 1;
} while (!value.compareAndSet(oldValue, newValue));
}
}
2. 版本号机制
数据库实现:表中添加
version字段,更新时验证版本。UPDATE table SET value = new_value, version = version + 1 WHERE id = ? AND version = ?;Java实现:
public class VersionedData { private String data; private long version; public boolean update(String newData, long expectedVersion) { if (expectedVersion != this.version) { return false; // 版本冲突,更新失败 } this.data = newData; this.version++; return true; } }
乐观锁的典型应用场景
数据库更新
- 在SQL语句中通过
WHERE version = ?实现行级锁。
- 在SQL语句中通过
缓存更新
- 使用
Compare-And-Set原子操作更新缓存值。
- 使用
并发容器
ConcurrentHashMap的computeIfAbsent()等方法使用乐观锁思想。
分布式系统
- 通过分布式版本号或时间戳实现最终一致性。
与悲观锁的对比
| 特性 | 乐观锁 | 悲观锁 |
|---|---|---|
| 加锁时机 | 仅在更新时检查冲突 | 读取时加锁 |
| 实现方式 | CAS、版本号 | synchronized、ReentrantLock |
| 适用场景 | 读多写少、冲突少 | 写多冲突多、长事务 |
| 线程状态 | 无阻塞,冲突时重试 | 阻塞等待锁释放 |
| 典型案例 | 数据库乐观锁、Atomic类 | 同步方法、数据库行锁 |
注意事项
ABA问题
- 问题:值从A→B→A,版本号不变,CAS误认为未修改。
- 解决方案:使用
AtomicStampedReference记录版本戳。
重试机制
- 冲突频繁时,需设置最大重试次数或退化为悲观锁。
长事务风险
- 长时间持有数据可能导致冲突概率升高,建议拆分事务。
Java中的乐观锁工具
原子类(JUC)
AtomicInteger、AtomicLong、AtomicReference等。
StampedLock
- JDK 8引入,支持乐观读锁,适用于读多写少场景。
import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private final StampedLock lock = new StampedLock(); private int value = 0; public int read() { long stamp = lock.tryOptimisticRead(); // 获取乐观读戳记 int currentValue = value; if (!lock.validate(stamp)) { // 检查戳记是否有效(是否有写操作) stamp = lock.readLock(); // 升级为悲观读锁 try { currentValue = value; } finally { lock.unlockRead(stamp); } } return currentValue; } }
总结
乐观锁通过假设无冲突来减少锁的使用,在高并发读场景中表现优异。它是数据库、缓存和分布式系统中实现高性能并发控制的关键技术。但需注意冲突处理和ABA问题,合理选择乐观锁与悲观锁的使用场景,才能发挥最佳性能。