Bootstrap

高级java每日一道面试题-2025年01月22日-JVM篇-乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

如果有遗漏,评论区告诉我进行补充

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

我回答:

一、乐观锁与悲观锁的概念

1. 乐观锁
  • 定义:乐观锁假设并发冲突发生的概率较低,因此不会主动加锁,而是在提交更新时检查是否有其他事务已经修改过数据;如果有,则拒绝提交或提示用户重新尝试。
  • 特点
    • 不会阻塞其他线程的操作,允许并发执行。
    • 在更新时通过版本号、时间戳或哈希值等方式验证数据是否被修改。
    • 适用于读多写少的场景,能显著提高并发性能。
2. 悲观锁
  • 定义:悲观锁假设并发冲突发生的概率较高,因此每次操作共享资源时都会加锁,以确保其他线程不能同时访问或修改该资源。
  • 特点
    • 加锁期间其他线程必须等待,直到当前持有锁的线程释放锁。
    • 阻塞式操作,可能导致性能瓶颈,特别是在高并发环境下。
    • 适用于写多读少或者对数据一致性要求极高的场景。

二、乐观锁的实现方式

1. 版本号机制
  • 描述:在数据库中为每条数据记录一个版本号字段,在更新操作时比较当前事务的版本号与数据库中的版本号是否一致。
  • 流程
    • 如果一致,则执行更新操作,并将版本号递增。
    • 如果不一致,则说明有其他线程已经修改了数据,需要重新尝试更新操作。
  • 示例(使用JPA的@Version注解):
@Entity
public class Product {
    @Id
    private Long id;
    
    @Version
    private Integer version;

    // 其他属性...
}
2. 时间戳机制
  • 描述:类似于版本号机制,但使用时间戳来记录数据的最后修改时间。
  • 流程:当执行更新操作时,会比较当前事务的时间戳与数据库中的时间戳是否一致,从而决定是否执行更新操作。
3. CAS(Compare-and-Swap)算法
  • 描述:CAS是一种无锁算法,它通过比较并交换的方式实现对共享变量的原子更新。
  • 流程:在更新数据前检查数据的当前值是否与预期值相等,如果相等则进行更新,否则重新尝试更新操作。
  • 示例(使用AtomicInteger类):
AtomicInteger counter = new AtomicInteger(0);

public boolean incrementIfEquals(int expected, int newValue) {
    return counter.compareAndSet(expected, newValue);
}

三、悲观锁的实现方式

1. synchronized 关键字
  • 描述:Java中最常见的实现悲观锁的方式之一。可以在方法或代码块上锁定对象,确保同一时间只有一个线程可以访问共享资源。
  • 示例
public synchronized void updateData() {
    // 修改数据的逻辑
}
2. Lock 接口
  • 描述:Java 5提供了Lock接口来替代synchronized关键字,提供了更灵活的锁机制。
  • 功能:支持公平锁和非公平锁、可中断的获取锁、尝试获取锁等多种功能。
  • 常见实现类ReentrantLock等。
  • 示例
private final ReentrantLock lock = new ReentrantLock();

public void updateData() {
    lock.lock();
    try {
        // 修改数据的逻辑
    } finally {
        lock.unlock();
    }
}
3. 数据库层面的悲观锁
  • 描述:在数据库中,悲观锁通常通过SQL语句实现,如使用SELECT ... FOR UPDATE语句来锁定查询到的行,阻止其他事务的读写操作。
  • 示例
SELECT * FROM products WHERE id = ? FOR UPDATE;

四、应用场景

1. 乐观锁的应用场景
  • 适用情况:适用于并发冲突较少、并发性能要求较高的场景。
  • 示例:电商网站的库存扣减,因为库存数据的变化不是非常频繁,可以使用乐观锁来提高并发性能。
2. 悲观锁的应用场景
  • 适用情况:适用于并发冲突较多、数据一致性要求较高的场景。
  • 示例:金融系统的资金转账,由于资金数据的变化非常敏感且频繁,需要使用悲观锁来确保数据的一致性和安全性。

总结

乐观锁和悲观锁是Java并发编程中两个重要的概念,它们各自具有不同的特点和适用场景:

  • 选择合适的锁策略:根据应用的具体需求选择适当的锁策略。对于写多读少或对数据一致性要求极高的场景,可以选择悲观锁;而对于读多写少的场景,乐观锁则更为适合。
  • 结合实际案例:在面试中能够准确阐述这些概念,并结合实际案例讨论其应用场景,将极大地展示你的专业水平和技术深度。
  • 优化性能:理解锁的工作原理可以帮助开发者避免常见的并发问题,并写出更高效的并发代码。例如,在高并发环境中,合理运用乐观锁可以大大提升系统的吞吐量和响应速度。
;