专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java 面试题:乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

temp10 2025-06-23 21:46:28 java教程 2 ℃ 0 评论

乐观锁与悲观锁的对比及实现方式


一、核心概念对比

维度

悲观锁

Java 面试题:乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观锁

设计理念

假设并发冲突必然发生,预先加锁防止数据修改

假设并发冲突较少发生,提交时检测冲突

适用场景

写操作频繁,数据竞争激烈

读多写少,冲突概率低

性能开销

加锁/释放锁带来较大开销,高并发下可能成为瓶颈

无锁设计,冲突检测时可能有重试开销

数据一致性

强一致性

最终一致性


二、实现方式详解

1. 悲观锁实现

Java 原生支持:

  • synchronized 关键字
public synchronized void updateData() { // 临界区操作(自动加锁/解锁) }
  • ReentrantLock
ReentrantLock lock = new ReentrantLock(); 
public void updateData() { lock.lock(); 

 try { // 临界区操作 
 } finally { 
   lock.unlock();
 } }

数据库实现:

  • SELECT ... FOR UPDATE(行级锁)
BEGIN; 
SELECT * FROM account WHERE id = 1 FOR UPDATE; 
UPDATE account SET balance = balance - 100 WHERE id = 1; 
COMMIT;

2. 乐观锁实现

Java CAS 操作:

  • Atomic 原子类
AtomicInteger count = new AtomicInteger(0); 
public void safeIncrement() { 
  int oldValue, newValue; 
  do { 
    oldValue = count.get(); 
    newValue = oldValue + 1; 
  } while (!count.compareAndSet(oldValue, newValue));
}

数据库版本号机制:

  • Version 字段控制
UPDATE product
SET stock = stock - 1, 
  version = version + 1 
WHERE id = 100 AND version = 2; -- 检查影响行数,若为0则重试

框架支持:

  • JPA/Hibernate
@Entity public class Product { 
  @Id private Long id; 
  private int stock;
  @Version private int version; // 自动管理版本号 
}

三、性能对比与选择策略

场景

推荐锁类型

理由

短事务高频写

悲观锁

避免频繁冲突导致的重试开销(如秒杀库存扣减)

长事务复杂操作

乐观锁

减少锁持有时间,防止长事务阻塞(如订单状态流转)

读多写少

乐观锁

无锁设计提升吞吐量(如商品详情浏览)

分布式系统

乐观锁

避免跨服务锁管理的复杂性(如基于版本号的分布式事务)


四、实现方案最佳实践

1. 高并发秒杀(悲观锁)

java

public boolean seckill(Long productId) {
    // 使用分布式锁(如Redisson)
    RLock lock = redisson.getLock("seckill:" + productId);
    try {
        lock.lock(3, TimeUnit.SECONDS); // 控制锁超时时间
        Product product = productDao.selectForUpdate(productId);
        if (product.getStock() > 0) {
            productDao.updateStock(productId);
            return true;
        }
        return false;
    } finally {
        lock.unlock();
    }
}

2. 购物车商品数量修改(乐观锁)

java

public boolean updateCartQuantity(Long cartId, int newQty) {
    Cart cart = cartDao.get(cartId);
    int retry = 0;
    while (retry < 3) { // 最大重试次数
        int oldVersion = cart.getVersion();
        cart.setQuantity(newQty);
        int rows = cartDao.updateWithVersion(
            cartId, newQty, oldVersion, oldVersion + 1
        );
        if (rows > 0) return true;
        cart = cartDao.get(cartId); // 重新加载最新数据
        retry++;
    }
    return false;
}

五、常见面试追问及应答

Q1:CAS操作有什么缺点?

  • ABA问题:通过版本号或时间戳解决(如AtomicStampedReference)
  • 自旋开销:冲突激烈时CPU占用高,需配合退避策略(如指数退避)

Q2:如何选择数据库乐观锁的实现方式?

  • 版本号字段:需额外维护字段,适合业务数据模型
  • 时间戳:精度可能不足,存在并发风险
  • 条件更新:直接在WHERE子句中校验原值(如WHERE stock = oldStock)

Q3:分布式场景下如何实现乐观锁?

  • Redis WATCH/MULTI:监控Key变化后执行事务
  • ZooKeeper版本号:znode的dataVersion属性控制
  • 分布式事务框架:Seata的AT模式通过全局锁实现

总结

  • 悲观锁:通过独占资源确保强一致性,适合写冲突频繁场景,但需注意死锁风险和性能损耗。
  • 乐观锁:通过版本控制实现无锁并发,适合读多写少场景,需处理冲突重试和ABA问题。

技术选型建议

  1. 优先评估业务场景的读写比例和冲突概率
  2. 简单场景用语言级锁(如synchronized),复杂场景用框架支持(如JPA乐观锁)
  3. 分布式系统需结合中间件(如Redis/ZooKeeper)实现跨服务锁管理

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表