时间:2025-09-08 21:00
人气:
作者:admin
乐观锁和悲观锁是处理数据竞争(多个线程可能同时修改同一数据)的两种不同策略。它们的区别源于对“冲突发生概率”的不同假设。
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 哲学 | 假设冲突很可能发生 | 假设冲突不太可能发生 |
| 机制 | 先取锁,再操作 | 先操作,更新前再检查冲突 |
| 实现 | synchronized, ReentrantLock, FOR UPDATE | CAS、版本号、原子变量 |
| 线程状态 | 其他线程会**被阻塞(挂起) ** | 其他线程不被阻塞,可继续执行,但可能需要重试 |
| 开销 | 大(加锁、解锁、上下文切换) | 小(无锁操作,但冲突时重试有开销) |
| 适用场景 | 写操作多,冲突频繁的场景 | 读操作多,冲突稀少的场景 |
悲观锁假定冲突非常可能发生。因此,它在访问共享数据之前,会先独占性地锁定资源,阻止其他任何线程访问,直到它完成操作并释放锁。
synchronized 关键字(Java)ReentrantLock 等显式锁(Java)SELECT ... FOR UPDATE(数据库中的行锁/表锁)优点:简单粗暴,能保证最高的数据安全性和一致性。
缺点:性能开销大。加锁和释放锁的操作本身消耗资源,更重要的是,线程的挂起和唤醒是非常昂贵的操作,会导致上下文切换。如果一个线程持有锁的时间很长,其他所有线程都会被阻塞,严重影响系统吞吐量。
写多读少的场景,即冲突发生的概率确实很高。在这种情况下,乐观锁会频繁失败重试,反而可能比悲观锁的直接阻塞性能更差。
乐观锁假定冲突不太可能发生。因此,它不会在访问数据时立即加锁,而是先直接操作数据。但在更新数据的那一刻,它会检查在此期间是否有其他线程修改过这个数据。如果没有,就更新成功;如果有,就更新失败,并进行相应的处理(通常是重试或报错)。
CAS指令:CPU级别的原子操作(Compare-And-Swap)。它是乐观锁技术的底层基石。
CompareAndSet(oldValue, newValue)。只有在当前值等于 oldValue 时,才会将其设置为 newValue。原子类:Java中的 AtomicInteger、AtomicLong、AtomicReference 等就是基于CAS实现的。
版本号机制:数据库和乐观锁框架(如Hibernate)中常用。在数据表中增加一个 version 字段。
优点:性能高。在没有冲突的情况下,它完全避免了加锁、解锁、线程阻塞和上下文切换的开销,吞吐量极高。
缺点:
读多写少的场景,即冲突发生的概率很低。这是它的理想舞台,能发挥其无锁化的巨大性能优势。
选择哪种锁,取决于你对冲突概率的判断。在高并发的互联网应用中,读远多于写是非常普遍的情况,因此乐观锁(及其变种)的应用更为广泛。
作者:AmyZYX