文章目录
- 乐观锁与悲观锁:概念、实现与应用场景
- 引言
- 1. 悲观锁(Pessimistic Locking)
- 1.1 定义
- 1.2 特点
- 1.3 实现方式
- 1.4 适用场景
- 2. 乐观锁(Optimistic Locking)
- 2.1 定义
- 2.2 特点
- 2.3 实现方式
- 2.4 适用场景
- 3. 乐观锁与悲观锁的对比
- 4. 实际应用中的选择
- 4.1 如何选择锁机制
- 4.2 混合使用
- 5. 代码示例
- 5.1 悲观锁示例
- 5.2 乐观锁示例
- 6. 总结
- 参考资料
乐观锁与悲观锁:概念、实现与应用场景
引言
在多线程编程和并发控制中,锁机制是保证数据一致性的重要手段。根据对并发冲突的处理方式,锁可以分为 乐观锁 和 悲观锁 两种类型。本文将详细介绍乐观锁和悲观锁的概念、实现方式以及适用场景,帮助读者更好地理解并选择合适的锁机制。
1. 悲观锁(Pessimistic Locking)
1.1 定义
悲观锁是一种保守的并发控制策略,它假设在数据访问过程中一定会发生冲突,因此在访问数据时会对数据进行加锁,确保其他线程无法修改数据。
1.2 特点
- 加锁时机:在访问数据时立即加锁。
- 适用场景:写操作多、读操作少的场景。
- 优点:保证数据强一致性,避免脏读和不可重复读。
- 缺点:加锁会降低并发性能,可能导致线程阻塞。
1.3 实现方式
- 数据库:使用
SELECT ... FOR UPDATE
语句锁定数据行。BEGIN; SELECT * FROM table WHERE id = 1 FOR UPDATE; UPDATE table SET value = 100 WHERE id = 1; COMMIT;
- Java:使用
synchronized
关键字或ReentrantLock
。synchronized (lock) {// 访问共享资源 }
1.4 适用场景
- 高冲突场景:当多个线程频繁修改同一数据时,悲观锁可以有效避免数据不一致。
- 强一致性要求:如银行转账、库存扣减等场景。
2. 乐观锁(Optimistic Locking)
2.1 定义
乐观锁是一种乐观的并发控制策略,它假设在数据访问过程中不会发生冲突,因此在访问数据时不会加锁,而是在提交更新时检查数据是否被修改。
2.2 特点
- 加锁时机:在提交更新时检查冲突。
- 适用场景:读操作多、写操作少的场景。
- 优点:提高并发性能,减少线程阻塞。
- 缺点:需要处理冲突,可能导致更新失败。
2.3 实现方式
-
数据库:使用版本号(Version)或时间戳(Timestamp)实现。
UPDATE table SET value = 100, version = version + 1 WHERE id = 1 AND version = 2;
如果更新失败,说明数据已被其他线程修改,需要重试或处理冲突。
-
Java:使用
Atomic
类或CAS
(Compare And Swap)操作。AtomicInteger version = new AtomicInteger(0); int currentVersion = version.get(); // 假设 value 是需要更新的共享变量 if (version.compareAndSet(currentVersion, currentVersion + 1)) {// 更新成功 } else {// 更新失败,处理冲突 }
2.4 适用场景
- 低冲突场景:当多个线程很少修改同一数据时,乐观锁可以提高并发性能。
- 高并发读操作:如新闻网站的文章阅读、社交媒体的点赞等场景。
3. 乐观锁与悲观锁的对比
特性 | 悲观锁 | 乐观锁 |
---|---|---|
加锁时机 | 访问数据时立即加锁 | 提交更新时检查冲突 |
适用场景 | 写操作多、读操作少 | 读操作多、写操作少 |
优点 | 保证数据强一致性 | 提高并发性能 |
缺点 | 加锁降低并发性能,可能导致线程阻塞 | 需要处理冲突,可能导致更新失败 |
实现方式 | synchronized 、ReentrantLock 、FOR UPDATE | 版本号、时间戳、CAS 操作 |
4. 实际应用中的选择
4.1 如何选择锁机制
- 悲观锁:适用于写操作频繁、冲突概率高的场景,如订单系统、库存管理。
- 乐观锁:适用于读操作频繁、冲突概率低的场景,如新闻阅读、点赞系统。
4.2 混合使用
在实际应用中,可以根据不同的业务场景混合使用乐观锁和悲观锁。例如:
- 在订单系统中,使用悲观锁保证库存扣减的一致性。
- 在新闻阅读系统中,使用乐观锁提高并发性能。
5. 代码示例
5.1 悲观锁示例
public class PessimisticLockExample {private int balance = 100;public synchronized void withdraw(int amount) {if (balance >= amount) {balance -= amount;System.out.println("Withdrawal successful. Remaining balance: " + balance);} else {System.out.println("Insufficient balance.");}}
}
5.2 乐观锁示例
public class OptimisticLockExample {private AtomicInteger balance = new AtomicInteger(100);public void withdraw(int amount) {while (true) {int currentBalance = balance.get();if (currentBalance >= amount) {if (balance.compareAndSet(currentBalance, currentBalance - amount)) {System.out.println("Withdrawal successful. Remaining balance: " + balance.get());break;}} else {System.out.println("Insufficient balance.");break;}}}
}
6. 总结
- 悲观锁:通过加锁保证数据一致性,适用于高冲突场景。
- 乐观锁:通过冲突检测提高并发性能,适用于低冲突场景。
- 选择依据:根据业务场景的特点选择合适的锁机制,必要时可以混合使用。
通过理解乐观锁和悲观锁的特点及适用场景,开发者可以更好地设计并发控制策略,提升系统的性能和稳定性。
参考资料
- Java 官方文档:
synchronized
、Atomic
类 - 《Java 并发编程实战》
- 《高性能 MySQL》