一、核心思想:心态决定机制
悲观锁和乐观锁是两种处理并发访问的策略,它们的核心区别在于对并发冲突的看法。
-
悲观锁(Pessimistic Locking) :
- 心态:认为并发冲突是常态,每次操作前都假设会发生冲突。
- 机制:在数据被访问前,强制加锁,阻止其他线程访问。
- 实现:
synchronized、ReentrantLock。
-
乐观锁(Optimistic Locking) :
- 心态:认为并发冲突是小概率事件,允许并发访问。
- 机制:在更新数据时,检查数据是否被其他线程修改过。如果被修改,则放弃本次更新或进行重试。
- 实现:
CAS(Compare-And-Swap)、版本号机制。
二、Java中的具体实现
1. 悲观锁
synchronized:Java 内置的关键字,可以修饰方法或代码块。它是一个重量级锁,能够保证原子性、可见性和有序性。ReentrantLock:java.util.concurrent包下的类,提供了比synchronized更灵活的锁控制。它支持可中断锁、超时锁和公平锁。
2. 乐观锁
-
CAS:CAS是一种无锁算法,它依赖于 CPU 的原子指令,可以在不加锁的情况下实现线程安全的数据更新。- 工作流程:
CAS包含三个参数:V(内存值)、A(旧的预期值)、B(新的值)。只有当V等于A时,才会将V更新为B。 - 实现:
AtomicInteger、AtomicLong等原子类。
- 工作流程:
-
版本号机制:在数据库中,通过为数据添加一个版本号字段。每次更新数据时,都会检查版本号是否一致,并将其加 1。如果版本号不一致,则说明数据已被修改,更新失败。
三、优缺点与选型指南
| 维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 性能 | 高并发写时性能差,锁竞争开销大。 | 高并发读时性能高,无锁。 |
| 数据安全 | 强一致性,避免脏数据。 | 可能需重试,需处理冲突。 |
| 适用场景 | 写操作多,冲突频繁,需要强一致性的场景。 | 读操作多,冲突较少,对性能要求高的场景。 |
| 实现复杂度 | 简单。 | 需处理冲突重试、ABA问题等。 |
结论:
悲观锁和乐观锁没有绝对的优劣之分,它们是两种不同的并发处理思想。在实际开发中,应根据业务场景(读写比例、一致性要求)进行权衡和选择。