什么是悲观锁
悲观锁总是考虑最坏的情况,认为共享资源每次拿到时都会有问题(如被修改),所以每次获取共享资源都会给共享资源上锁,此时其他线程想要获取资源就会一直阻塞直到锁释放。
总结:共享资源只会给一个线程使用,其他线程阻塞,用完后再交给其他线程。
什么是乐观锁
乐观锁每次总是考虑最好的情况,也就是每次拿到数据都认为数据没有问题,不需要加锁等待,只是需要在提交修改的时候检查一下资源是否被其他线程修改过,可以使用版本号或者 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 类,提供了 compareAndSwapObject、compareAndSwapInt、compareAndSwapLong
Atomic 就依赖于 CAS 乐观锁来保证其方法的原子性
CAS 的 ABA 问题
如果一个变量 V 首次读取时是 A 值,并且在准备赋值的时候检查到其仍然是 A 值,这并不能保证其并没有被其他线程修改过,因为在这期间可能被修改为了其他值但是又被改回了当前值,这就是 ABA 问题。
ABA 问题的解决思路是在变量前面加上版本号或者时间戳,JDK1.5之后的 AtomicStampedReference 类就是用于解决 ABA 问题的,其中 compareAndSet()方法就会首先检查当前引用是否等于预期引用,并且当前标志是否等于当前标志(版本号),如果全部相等,则以原子方式将该引用和该标志(版本号)都进行更新。
循环时间长开销大
CAS 会一直重试,直到成功,长时间不成功就会给 CPU 很大压力。
只能保证一个共享变量的原子操作
CAS 仅能对单个共享变量其作用。但是从 JDK1.5 开始,Java 提供了 AtomicReference来保证引用对象之间的原子性。通过将多个变量封装在一个对象中,我们就可以通过 CAS 来进行 CAS 操作。

Comments NOTHING