什么是乐观锁?乐观锁底层是如何实现的?
乐观锁是一种并发控制的策略。在操作数据的时候,线程读取数据的时候不会进行加锁,先去查询原值,操作的时候比较原来的值,看一下是都被其他线程修改,如果没有修改则写回,否则就重新执行读取流程
悲观锁(底层是synchronized和ReentrantLock)就是考虑事情比较悲观,认为在访问共享资源的时候发生冲突的概率比较高,所以每次访问前线程都需要加锁
乐观锁底层是通过CAS机制实现的,CAS机制包含三个组件:内存地址V、预期值A和新值B
CAS的操作过程如下:
- 比较内存地址V中的值是否与预期值A相等
- 如果相等,将内存地址V的值改成新值B
- 如果不相等,表示预期值A和实际值不匹配,操作失败
但是CAS并不完美,如果数据值一直发生改变,那么CAS会一直自旋,CPU会有巨大开销,而且CAS会有一个经典问题ABA问题
什么是ABA问题?如何解决ABA问题?
我们捋顺一下这张流程图:
- 线程一读取了数据A
- 线程二读取了数据A
- 线程二通过CAS比较,发现数据A是没错的,修改数据A为B
- 线程三读取数据B
- 线程三通过CAS比较,发现数据B是没错的,修改数据B为A
- 线程一通过CAS比较,发现数据A是没错的,修改数据A为B
这个过程中任何线程都没有做错什么,但是值被改变了,线程一却没有办法发现,其实这样得情况出现对结果是没有任何影响的,但是我们要做到规范,所以如何防止ABA问题呢?
加标志位:搞一个自增的字段,操作一次就自增一次
例如:我们需要操作数据库,根据CAS的原则我们本来只需要查询原本值就可以,现在我们一同查出他们的标志位版本字段version
只查询原本值不能防止ABA
update table set value = newValue where value = #{oldValue}
加上标志位version
update table set value = newValue ,version = version + 1 where value = #{oldValue} and vision = #{vision}
// 判断原来的值和版本号是否匹配,中间有别的线程修改,值可能相等,但是版本号100%不一样