Java事务与并发控制:乐观锁到分布式锁详解
Java事务与并发控制深度解析:从乐观锁到分布式锁
一、乐观锁实现方案
乐观锁是一种假设并发冲突不常发生的锁策略,适合读多写少的场景。
1. 版本号机制(@Version)
@Entity
public class Product {
@Id
private Long id;
private String name;
private int stock;
@Version // 关键注解
private int version;
// getters/setters...
}
工作原理:
- 读取数据时获取version值
- 更新时检查version是否变化
- 若version未变则更新成功并自增version
- 若version已变则抛出OptimisticLockException
实践建议:
- 适合库存扣减、订单状态变更等场景
- 结合Spring的
@Retryable
实现自动重试 - 版本号字段建议使用整型或时间戳
2. CAS操作
AtomicInteger counter = new AtomicInteger(0);
// 典型CAS操作
boolean success = counter.compareAndSet(expect, update);
底层实现:
实践建议:
- Java中的
AtomicXXX
类基于CPU的CAS指令 - 注意ABA问题(可通过版本号或
StampedReference
解决) - 高竞争场景下性能会下降
二、悲观锁应用场景
悲观锁假设并发冲突经常发生,适合写多读少的场景。
1. SELECT FOR UPDATE
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 执行余额操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
锁升级情况:
实践建议:
- MySQL中InnoDB的行锁基于索引实现
- 没有命中索引会升级为表锁
- 锁等待超时可通过
innodb_lock_wait_timeout
配置
2. 死锁检测与避免
常见死锁场景:
// 线程1
synchronized(resourceA) {
synchronized(resourceB) { ... }
}
// 线程2
synchronized(resourceB) {
synchronized(resourceA) { ... }
}
解决方案:
- 统一资源获取顺序
- 使用
tryLock
设置超时时间 JVM级死锁检测:
jstack <pid> | grep -i deadlock
数据库死锁日志分析:
SHOW ENGINE INNODB STATUS;
三、分布式锁与事务结合
1. Redis RedLock算法
实现步骤:
- 获取当前毫秒时间戳
- 依次尝试从N个独立Redis实例获取锁
- 计算获取锁总耗时,小于锁过期时间则获取成功
- 实际业务处理时间应小于锁有效期的1/3
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("orderLock");
try {
boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
// 处理业务
}
} finally {
lock.unlock();
}
2. Zookeeper分布式锁
临时顺序节点实现:
获取锁流程:
- 创建临时顺序节点
- 检查自己是否是最小节点
- 如果是则获取锁,否则监听前一个节点
- 当前一个节点删除时被唤醒
实践建议:
- 使用Curator框架的
InterProcessMutex
- 注意"羊群效应"(所有节点监听同一节点)
- 会话超时时间应合理设置
四、最佳实践总结
选型建议:
- 单机低竞争:乐观锁
- 单机高竞争:悲观锁
- 简单分布式:Redis锁
- 强一致需求:Zookeeper锁
性能指标:
锁类型 吞吐量 延迟 一致性保证 乐观锁 高 低 最终 悲观锁 中 中 强 Redis锁 高 低 最终 Zookeeper锁 低 高 强 事务整合要点:
- 锁的粒度应大于等于事务粒度
- 锁的持有时间应覆盖整个事务
- 考虑引入事务管理器(如Spring的
TransactionSynchronizationManager
)
通过合理选择并发控制策略,可以在保证数据一致性的同时获得最佳性能表现。