一: 概念:
乐观锁和悲观锁是一种广义上的概念,体现了看待线程同步的不同角度.在java和数据库中都有对此概念的实际运用
对于同一个数据的并发操作
乐观锁:
乐观锁认为自己在使用数据的时候,不会有其它线程修改数据,所以不会添加锁,只是在更新数据之前去判断有没有别的线程去修改过这个数据,如果这个数据没有被修改,当前线程将自己修改的数据成功写入.
如果数据已经被其它线程修改, 就会根据不同的实现方式执行不同的操作
悲观锁:
悲观锁认为自己在使用数据的时候一定有别的线程去修改过数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程去修改.
[例如java中的synchronized关键字和lock的实现类都是悲观锁]{.underline}
二: 使用场景
乐观锁: 适用与读操作较多的场景,提高系统的吞吐量
悲观锁: 适用于写操作较多的场景, 保证数据的稳定性
三: 实现方式
为什么乐观锁能够做到不锁定同步资源也可以正确的实现线程同步呢?
CAS全称 Compare And
Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
(1): java.util.concurrent包中的原子类
根据定义我们可以看出各属性的作用:
-
unsafe: 获取并操作内存的数据。
-
valueOffset: 存储value在AtomicInteger中的偏移量。
-
value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的。
(2): CAS算法
(1): CAS全称 Compare And
Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
(2): CAS算法涉及到三个操作数:
-
需要读写的内存值 V。
-
进行比较的值 A。
-
要写入的新值 B。
(3): 缺点
CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:
- ABA问题。[CAS需要在操作值的时候检查内存值是否发生变化]{.underline},没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从"A-B-A"变成了"1A-2B-3A"。
JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。
只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。