悲观锁与乐观锁

ouyu69 发布于 12 天前 7 次阅读


什么是悲观锁

悲观锁总是考虑最坏的情况,认为共享资源每次拿到时都会有问题(如被修改),所以每次获取共享资源都会给共享资源上锁,此时其他线程想要获取资源就会一直阻塞直到锁释放。

总结:共享资源只会给一个线程使用,其他线程阻塞,用完后再交给其他线程。


什么是乐观锁

乐观锁每次总是考虑最好的情况,也就是每次拿到数据都认为数据没有问题,不需要加锁等待,只是需要在提交修改的时候检查一下资源是否被其他线程修改过,可以使用版本号或者 CAS 算法。


  • 悲观锁常用于多写场景,竞争激烈,避免频繁失败和重试影响性能。
  • 乐观锁常用于多读场景,竞争较少,这样可以避免频繁加锁影响性能。

如何实现乐观锁

版本号机制

在数据库表中添加一个 version 字段,表示数据被修改的次数。当数据被修改的时候,version 会 + 1 。当线程 A 要更新数据的时候,若刚才读到的 version 和 数据库中的相等时才会更新,否则一直重试,直到成功。

CAS 算法

全称是 比较和交换(Compare And Swap),用于实现乐观锁,被广泛用于各大框架中。CAS 很简单,就是用一个预期的值和要更新的变量进行比骄,一致才会更新。

CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。

CAS 涉及到三个操作数

  • V:要更新的变量值 Var
  • E:预期值 Expected
  • N:拟写入的新值 New

当且仅当 V 的值等于 E 时,CAS 通过原子的方式用 NEW 来更新 V。如果不相等,说明有其他线程更新了 V ,则当前线程放弃更新。

具体实现是使用 sun.misc 包下的 Unsafe 类,提供了 compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong

Atomic 就依赖于 CAS 乐观锁来保证其方法的原子性

CAS 的 ABA 问题

如果一个变量 V 首次读取时是 A 值,并且在准备赋值的时候检查到其仍然是 A 值,这并不能保证其并没有被其他线程修改过,因为在这期间可能被修改为了其他值但是又被改回了当前值,这就是 ABA 问题。

ABA 问题的解决思路是在变量前面加上版本号或者时间戳,JDK1.5之后的 AtomicStampedReference 类就是用于解决 ABA 问题的,其中 compareAndSet()方法就会首先检查当前引用是否等于预期引用,并且当前标志是否等于当前标志(版本号),如果全部相等,则以原子方式将该引用和该标志(版本号)都进行更新。

循环时间长开销大

CAS 会一直重试,直到成功,长时间不成功就会给 CPU 很大压力。

只能保证一个共享变量的原子操作

CAS 仅能对单个共享变量其作用。但是从 JDK1.5 开始,Java 提供了 AtomicReference来保证引用对象之间的原子性。通过将多个变量封装在一个对象中,我们就可以通过 CAS 来进行 CAS 操作。


我打算法竞赛,真的假的。
最后更新于 2025-11-23