1.1 简介
乐观锁和悲观锁是数据库中引入名词,可以理解是一种思想,在 Java
并发编程中也有所体现,例如 synchronized
关键字和 ReentrantLock
等都是采用悲观锁。
乐观锁,顾名思义就是看待问题很乐观,每次去拿数据时候,都认为别人没有对数据偷偷摸摸修改过。
悲观锁,则相反,看待问题非常悲观,每次去拿数据,总认为别人会偷偷摸摸对数据进行不可告人的修改,所以为了安全,每次去拿数据时候,都会上锁,别人要想拿数据就会阻塞。
1.2 乐观锁
乐观锁由于其特性,多应用于读多写少的场景,加大整个系统的吞吐量。乐观锁实现有两种实现方式:
- 版本号机制
- CAS 算法
1.2.1 版本号机制
在数据库表中加上一个数据库版本号 version
字段,当数据库被修改或者更新时候 version
的值加 1 。简单来说,当线程 A 要更新数据时,在读取数据同时会读取到 version
的值,在提交更新时候,假如读取到的 version
值与当前数据库 version
相等是才更新,否则重试更新操作。
1.2.2 CAS 算法
比较与交换(Compare and Swap),是一种无锁的算法,不同 synchronized
阻塞算法,CAS 是非阻塞的算法,在不加锁的情况下实现多线程之间变量的同步,三个操作数:
- 内存值 V
- 旧的预期值 A
- 要修改的新值 B
只有预期值 A 和内存值 V 相同,将内存值修改为 B。
1.2.2.1 CAS 在 JDK 中的应用
- 原子类变量,如
java.util.concurrent.atomic
中AtomicXXX
; java.util.concurrent
中大多数类在实现时候都直接或者间接使用原子变量类。
1.2.2.2 CAS 原理
CAS
在 Java
中靠 Unsafe
这个核心类来实现,Java
无法直接访问底层操作系统,而是通过 JNI
来访问,Unsafe
相当于 JVM
一个后门,提供硬件级别的原子操作。
1.2.2.3.CAS 缺点
- 循环时间长,开销大(自旋 CAS,如果操作不成功,会一直循环执行,直到成功,如果一直不成功,一直循环,会给 CPU 带来非常大的开销)
- 只能保证一个共享变量的原子操作(只能对一个共享变量执行操作,但是 JDK5 开始提供了
AtomicReference
类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS操作 ) ABA
问题(1.线程 1 从内存 V 取出 A;2.线程 2 从内存中取出 A,并且执行操作变成 B,接着又把 V 位置数据变成 A;3.线程 1 进行 CAS 操作发现内存还是 A,殊不知它已经被被人(线程 2)偷偷摸摸动过手脚了)。从 Java1.5 开始atomic
提供类AtomicStampedReference
来解决 ABA 问题,两步判断,先判断当前引用是否等于预期引用,接着判断当前标志是否等于预期标志,全部相等,才能操作。
1.3 悲观锁
悲观锁由于其特性,多应用于写多读少的场景,Java
中 synchronized
和 ReentrantLock
等独占锁就是典型的悲观锁思想。