在Java并发编程中,理解不同类型的锁及其适用场景至关重要。下面这个表格汇总了乐观锁、悲观锁、公平锁和非公平锁的核心特性和典型实现,希望能帮助你快速建立整体概念。
| 锁类型 | 核心思想/假设 | 典型实现举例 |
|---|---|---|
| 乐观锁 | 假设并发冲突概率很小,先操作数据,提交时再检测冲突。 | 1. 版本号/时间戳机制 (常用于数据库) 2. 原子类 (java.util.concurrent.atomic包下的 AtomicInteger等,基于CAS) |
| 悲观锁 | 假设并发冲突概率很高,访问数据前先加锁,确保操作期间的排他性。 | 1. synchronized关键字 2. ReentrantLock等 Lock接口实现类 3. 数据库悲观锁 (如 SELECT ... FOR UPDATE) |
| 公平锁 | 按照线程请求锁的先后顺序来分配锁,保证先到先得,避免线程"饥饿"。 | 1. ReentrantLock(true) (构造时传入 true) 2. Semaphore等并发工具也可配置为公平模式 |
| 非公平锁 | 允许"插队",线程获取锁的顺序不一定与请求顺序一致,可能提高吞吐量。 | 1. synchronized关键字 (内置实现即为非公平锁) 2. ReentrantLock()/ ReentrantLock(false) (默认或显式设置为非公平) |
💡 项目实战与选择策略
了解概念和实现后,关键在于如何在实战中做出正确选择。
🔐 乐观锁 vs 悲观锁
选择取决于并发冲突的概率 和应用场景。
-
乐观锁适用场景 :读多写少,冲突概率低的场景,如:
- 商品信息更新:多个管理员可能同时修改商品描述,但冲突概率不高。
- 用户积分微调:并发修改次数相对较少。
- 数据库并发更新:通过版本号字段控制。
ini// 示例:基于版本号的乐观锁数据库更新模式 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("数据已被其他事务修改"); } -
悲观锁适用场景 :写多读少 ,冲突概率高,或需要强一致性的场景,如:
- 银行转账:涉及关键资金,必须保证绝对准确。
- 库存扣减/秒杀:高并发下防止超卖。
- 数据库事务中锁定关键记录。
csharp// 示例:使用 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 非公平锁
选择取决于对线程执行顺序(公平性)的要求 和系统吞吐量的权衡。
-
公平锁适用场景 :需要严格保证线程不会"饥饿",对公平性要求高于性能的场景,如:
- 订票系统:保证先到先得。
- 任务调度:需要按提交顺序执行任务。
java// 示例:创建公平锁 private final ReentrantLock fairLock = new ReentrantLock(true); // 传入true创建公平锁 -
非公平锁适用场景 :绝大多数情况 ,为了追求更高的系统吞吐量,如:
- 大部分业务逻辑处理。
- 高性能服务器端应用。
java// 示例:创建非公平锁(默认行为) private final ReentrantLock nonFairLock = new ReentrantLock(); // 默认即为非公平锁 // 或显式声明 // private final ReentrantLock nonFairLock = new ReentrantLock(false);
🚀 锁性能优化实战要点
- 减小锁粒度:只锁必要的代码块,而不是整个方法。
- 缩短锁持有时间:避免在锁内执行IO、网络请求等耗时操作。
- 锁分离 :读多写少时,优先考虑
ReadWriteLock或StampedLock。 - 无锁化设计 :对于简单的状态标记、计数器,优先使用
Atomic原子类。 - 避免锁嵌套:容易导致死锁。