Java并发编程:乐观锁、悲观锁、公平锁、非公平锁

212 阅读3分钟

在Java并发编程中,理解不同类型的锁及其适用场景至关重要。下面这个表格汇总了乐观锁、悲观锁、公平锁和非公平锁的核心特性和典型实现,希望能帮助你快速建立整体概念。

锁类型核心思想/假设典型实现举例
乐观锁假设并发冲突概率很小,先操作数据,提交时再检测冲突。1. ​版本号/时间戳机制​ (常用于数据库)
2. ​原子类​ (java.util.concurrent.atomic包下的 AtomicInteger等,基于CAS)
悲观锁假设并发冲突概率很高,访问数据前先加锁,确保操作期间的排他性。1. ​synchronized关键字
​ 2. ​ReentrantLockLock接口实现类
​ 3. ​数据库悲观锁​ (如 SELECT ... FOR UPDATE)
公平锁按照线程请求锁的先后顺序来分配锁,保证先到先得,避免线程"饥饿"。1. ​ReentrantLock(true)
​ (构造时传入 true)
2. ​Semaphore等并发工具也可配置为公平模式​
非公平锁允许"插队",线程获取锁的顺序不一定与请求顺序一致,可能提高吞吐量。1. ​synchronized关键字​ (内置实现即为非公平锁)
2. ​ReentrantLock()/ ReentrantLock(false) ​ (默认或显式设置为非公平)

💡 项目实战与选择策略

了解概念和实现后,关键在于如何在实战中做出正确选择。

🔐 乐观锁 vs 悲观锁

选择取决于并发冲突的概率应用场景

  • 乐观锁适用场景​:​读多写少,冲突概率低的场景,如:

    • 商品信息更新​:多个管理员可能同时修改商品描述,但冲突概率不高。
    • 用户积分微调​:并发修改次数相对较少。
    • 数据库并发更新​:通过版本号字段控制。
    // 示例:基于版本号的乐观锁数据库更新模式
    String sql = "UPDATE products SET name = ?, price = ?, version = version + 1 WHERE id = ? AND version = ?";
    int affectedRows = jdbcTemplate.update(sql, newName, newPrice, productId, currentVersion);
    if (affectedRows == 0) {
        // 处理更新失败(版本号不匹配),通常重试或抛出异常
        throw new OptimisticLockingFailureException("数据已被其他事务修改");
    }
    
  • 悲观锁适用场景​:​写多读少,冲突概率高,或需要强一致性的场景,如:

    • 银行转账​:涉及关键资金,必须保证绝对准确。
    • 库存扣减/秒杀​:高并发下防止超卖。
    • 数据库事务中锁定关键记录
    // 示例:使用 ReentrantLock 的悲观锁
    public class BankAccountService {
        private final ReentrantLock lock = new ReentrantLock();
        public void transfer(Account from, Account to, BigDecimal amount) {
            lock.lock(); // 获取锁
            try {
                // 执行转账业务逻辑
                if (from.getBalance().compareTo(amount) >= 0) {
                    from.debit(amount);
                    to.credit(amount);
                }
            } finally {
                lock.unlock(); // 务必在finally块中释放锁
            }
        }
    }
    

⚖️ 公平锁 vs 非公平锁

选择取决于对线程执行顺序(公平性)的要求系统吞吐量的权衡

  • 公平锁适用场景​:需要严格保证线程不会"饥饿"​,对公平性要求高于性能的场景,如:

    • 订票系统​:保证先到先得。
    • 任务调度​:需要按提交顺序执行任务。
    // 示例:创建公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true); // 传入true创建公平锁
    
  • 非公平锁适用场景​:​绝大多数情况,为了追求更高的系统吞吐量,如:

    • 大部分业务逻辑处理
    • 高性能服务器端应用
    // 示例:创建非公平锁(默认行为)
    private final ReentrantLock nonFairLock = new ReentrantLock(); // 默认即为非公平锁
    // 或显式声明
    // private final ReentrantLock nonFairLock = new ReentrantLock(false);
    

🚀 锁性能优化实战要点

  1. 减小锁粒度​:只锁必要的代码块,而不是整个方法。
  2. 缩短锁持有时间​:避免在锁内执行IO、网络请求等耗时操作。
  3. 锁分离​:读多写少时,优先考虑 ReadWriteLockStampedLock
  4. 无锁化设计​:对于简单的状态标记、计数器,优先使用 Atomic原子类。
  5. 避免锁嵌套​:容易导致死锁。