文章目录
乐观锁与悲观锁:概念、实现与应用场景
引言
在多线程编程和并发控制中,锁机制是保证数据一致性的重要手段。根据对并发冲突的处理方式,锁可以分为 乐观锁 和 悲观锁 两种类型。本文将详细介绍乐观锁和悲观锁的概念、实现方式以及适用场景,帮助读者更好地理解并选择合适的锁机制。
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》
转载自CSDN-专业IT技术社区
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_46146718/article/details/145774727