Java事务与并发控制深度解析:从乐观锁到分布式锁

一、乐观锁实现方案

乐观锁是一种假设并发冲突不常发生的锁策略,适合读多写少的场景。

1. 版本号机制(@Version)

@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private int stock;
    
    @Version  // 关键注解
    private int version;
    // getters/setters...
}

工作原理

  1. 读取数据时获取version值
  2. 更新时检查version是否变化
  3. 若version未变则更新成功并自增version
  4. 若version已变则抛出OptimisticLockException

实践建议

  • 适合库存扣减、订单状态变更等场景
  • 结合Spring的@Retryable实现自动重试
  • 版本号字段建议使用整型或时间戳

2. CAS操作

AtomicInteger counter = new AtomicInteger(0);

// 典型CAS操作
boolean success = counter.compareAndSet(expect, update);

底层实现

图1

实践建议

  • 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;

锁升级情况

图2

实践建议

  • MySQL中InnoDB的行锁基于索引实现
  • 没有命中索引会升级为表锁
  • 锁等待超时可通过innodb_lock_wait_timeout配置

2. 死锁检测与避免

常见死锁场景

// 线程1
synchronized(resourceA) {
    synchronized(resourceB) { ... }
}

// 线程2
synchronized(resourceB) {
    synchronized(resourceA) { ... }
}

解决方案

  1. 统一资源获取顺序
  2. 使用tryLock设置超时时间
  3. JVM级死锁检测:

    jstack <pid> | grep -i deadlock
  4. 数据库死锁日志分析:

    SHOW ENGINE INNODB STATUS;

三、分布式锁与事务结合

1. Redis RedLock算法

实现步骤

  1. 获取当前毫秒时间戳
  2. 依次尝试从N个独立Redis实例获取锁
  3. 计算获取锁总耗时,小于锁过期时间则获取成功
  4. 实际业务处理时间应小于锁有效期的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分布式锁

临时顺序节点实现

图3

获取锁流程

  1. 创建临时顺序节点
  2. 检查自己是否是最小节点
  3. 如果是则获取锁,否则监听前一个节点
  4. 当前一个节点删除时被唤醒

实践建议

  • 使用Curator框架的InterProcessMutex
  • 注意"羊群效应"(所有节点监听同一节点)
  • 会话超时时间应合理设置

四、最佳实践总结

  1. 选型建议

    • 单机低竞争:乐观锁
    • 单机高竞争:悲观锁
    • 简单分布式:Redis锁
    • 强一致需求:Zookeeper锁
  2. 性能指标

    锁类型吞吐量延迟一致性保证
    乐观锁最终
    悲观锁
    Redis锁最终
    Zookeeper锁
  3. 事务整合要点

    • 锁的粒度应大于等于事务粒度
    • 锁的持有时间应覆盖整个事务
    • 考虑引入事务管理器(如Spring的TransactionSynchronizationManager

通过合理选择并发控制策略,可以在保证数据一致性的同时获得最佳性能表现。

添加新评论