Java中的乐观锁和悲观锁,这两者是并发控制的重要概念,用于处理多线程环境下数据一致性问题。
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 基本思想 | 假定会发生并发冲突,提前加锁 | 假定不会发生冲突,提交时检测冲突 |
| 锁机制 | 显式加锁(synchronized, ReentrantLock等) | 无显式加锁,通过版本号/时间戳实现 |
| 并发性能 | 低(阻塞其他线程) | 高(无阻塞,允许并发操作) |
| 适用场景 | 写操作多、冲突概率高的场景 | 读操作多、冲突概率低的场景 |
| 数据一致性 | 强一致性 | 最终一致性 |
| 实现复杂度 | 简单 | 较复杂(需处理冲突重试) |
| 典型实现 | synchronized, ReentrantLock | CAS, 版本号机制, StampedLock |
| 失败处理 | 线程阻塞等待 | 回滚并重试或放弃操作 |
实现原理
认为并发冲突一定会发生,因此在访问数据前先加锁,确保同一时间只有一个线程能操作数据。
核心实现
// 1. synchronized 关键字
public synchronized void updateBalance(int amount) {
// 操作共享资源
}
// 2. ReentrantLock
private final Lock lock = new ReentrantLock();
public void updateBalance(int amount) {
lock.lock();
try {
// 操作共享资源
} finally {
lock.unlock();
}
}
强一致性保证
实现简单直观
性能开销大(线程阻塞)
死锁风险
降低系统吞吐量
实现原理
认为并发冲突很少发生,操作时不加锁,提交时检查数据是否被修改。
核心实现
// 1. CAS (Compare and Swap)
AtomicInteger balance = new AtomicInteger(100);
public void updateBalance(int amount) {
int current;
do {
current = balance.get();
} while (!balance.compareAndSet(current, current + amount));
}
// 2. 版本号机制(数据库)
// SQL示例
UPDATE account
SET balance = new_balance, version = version + 1
WHERE id = 123 AND version = current_version;
// 3. Java 8 StampedLock(乐观读)
private final StampedLock lock = new StampedLock();
private double balance;
public double readBalance() {
long stamp = lock.tryOptimisticRead(); // 获取乐观读锁
double current = balance; // 读取值
if (!lock.validate(stamp)) { // 检查是否被修改
stamp = lock.readLock(); // 升级为悲观读锁
try {
current = balance;
} finally {
lock.unlockRead(stamp);
}
}
return current;
}
高并发性能
避免死锁
提高系统吞吐量
实现复杂(需处理冲突)
ABA问题(可通过版本号解决)
不适合高冲突场景
// 悲观锁实现
public class InventoryPessimistic {
private int stock = 100;
private final Object lock = new Object();
public boolean deductStock() {
synchronized(lock) {
if (stock > 0) {
stock--;
return true;
}
return false;
}
}
}
// 乐观锁实现
public class InventoryOptimistic {
private AtomicInteger stock = new AtomicInteger(100);
public boolean deductStock() {
int current;
do {
current = stock.get();
if (current <= 0) return false;
} while (!stock.compareAndSet(current, current - 1));
return true;
}
}
100线程并发测试:
- 悲观锁:耗时 120ms,吞吐量 833 ops/s
- 乐观锁:耗时 35ms,吞吐量 2857 ops/s
// 根据冲突概率动态选择锁
public class AdaptiveLock {
private int conflictCounter = 0;
private final ReentrantLock pessimisticLock = new ReentrantLock();
private final AtomicInteger value = new AtomicInteger(0);
public void update() {
// 低冲突时使用乐观锁
if (conflictCounter < 5) {
if (tryOptimisticUpdate()) return;
}
// 高冲突时使用悲观锁
pessimisticLock.lock();
try {
// 执行更新
conflictCounter++; // 冲突计数增加
} finally {
pessimisticLock.unlock();
}
}
private boolean tryOptimisticUpdate() {
// 乐观锁实现...
}
}
// 使用AtomicStampedReference
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 0); // 初始值+版本号
public void update() {
int[] stampHolder = new int[1];
int current;
do {
current = ref.get(stampHolder); // 获取值和当前版本
int newStamp = stampHolder[0] + 1;
} while (!ref.compareAndSet(current, current + 10,
stampHolder[0], newStamp));
}
@Transactional
public void updateProduct(Product product) {
// 1. 获取当前版本
int currentVersion = product.getVersion();
// 2. 更新操作
int rows = productRepository.update(
product.getName(),
product.getPrice(),
currentVersion + 1, // 新版本
product.getId(),
currentVersion // 旧版本(校验条件)
);
// 3. 处理冲突
if (rows == 0) {
throw new OptimisticLockException("数据已被修改,请重试");
}
}
黄金法则:
- 当写操作 > 读操作时 → 选择悲观锁
- 当读操作 > 写操作时 → 选择乐观锁
- 在分布式系统中 → 优先考虑乐观锁(避免分布式锁性能问题)
通过合理选择锁机制,可以显著提升Java应用的并发性能和系统吞吐量,同时保证数据的一致性。