- 什么是悲观锁,什么是乐观锁?
悲观锁:顾名思义就是比较悲观的,就是对程序进行加锁处理,一次只有一个线程能进行操作,保证线程的安全性。
乐观锁:顾名思义就是乐观的,乐观锁实际并没有加锁,是通过程序控制来保证线程的安全性的。 - 悲观锁和乐观锁对比
对比:
悲观锁是重量级锁,乐观锁是轻量级锁。
悲观锁效率低,乐观锁效率高。
悲观锁是通过实际加锁来控制程序运行,乐观锁是通过程序控制来保证线程安全的。 - 悲观锁原理
悲观锁是属于重量级锁,是通过对程序加锁来保证线程的安全性的,比如我们常用的synchronized,ReentrantLock等,高并发下,当多个线程进入程序的时候,谁获取了锁,谁来运行程序,没有获取到锁的线程就继续等待。 - 乐观锁原理
乐观锁属于轻量级锁。是通过程序来控制保证线程安全性的,比如CAS无锁机制,高并发下,当多个线程进入程序的时候,通过原始值与原始预期值的对比,或者通过版本号来进行控制,只有一个线程可以运行程序,其他线程则继续循环运行。 - 以synchronized和CAS为例进行分析
synchronized:synchronized是属于重量级锁,jdk1.6之前,synchronized的效率特别低,主要是因为当多个线程并发访问的时候,一个线程获取了锁,则其他线程会等待,且线程由运行状态切换到了阻塞状态再切换到就绪状态,由于状态的切换导致效率降低,因为当线程进入到就绪状态,要再次进入运行状态则需要cpu的调度,增加了开销,jdk1.6版本之后,oracle对synchronized进行了优化,增加了短暂的自旋,即当并发访问的时候,一个线程获取了锁,其他线程不是直接进行状态的切换,而是进入短暂的自旋重试状态,如果当前获取了锁的线程很快释放了锁,则其他线程就可以直接获取锁而运行,而不是直接进行状态切换等待cpu调度,由此减少了时间和资源开销。
CAS:CAS是属于轻量级锁,程序并没有加锁,CAS里面有三个参数,内存值V,这个值是所有线程共享的,可以理解成这个值是加了volatile修饰,原始预期值A,新值B,名词比较拗口,我来简单解释一下,比如内存里面有一个值为1,我需要给1加5,则内存值V就是1,原始预期值A也是1,新值就是6,CAS的原理就是比较V和A的值是否相等,如果相等则进行修改,否则不修改,由于CAS并不是实际加锁,所以并发情况下,其他线程也能操作该值,我们举个例子,现在有两个线程,线程1和线程2,线程1要加5,线程2要减3,现在两个线程并发访问,首先线程1读取了该值,得到V=1,A=1,B=6,而线程1还没有操作时,cpu时间片被线程2抢走了,线程2得到V=1,A=1,B=-2,线程2获取V值判断V等于A,所以这个时候V就变成了-2,线程2执行完毕,线程1继续执行,当线程1判断V是否等于A时发现V已经变成了-2,而A还是1,V≠A,所以继续循环,重新获取V的值,则V=-2,那么则修改A=-2,B=3,然后再判断V==A,所以修改该值为3,我们可以看到,同一时间只有一个线程在操作该值,保证了线程的安全性,是因为在修改该值之前需要进行判断V和A是否相等,只有相等才能操作,否则继续循环。
-
悲观锁和乐观锁各自的缺点
悲观锁:效率低,重量级锁,是独占锁。
乐观锁:可能出现ABA问题和循环开销大问题。
ABA:
所谓ABA问题就是,线程1获取该值等于A,则V=‘A’,A=‘A’,
线程2修改该值等于B,则V=‘B’,
线程3修改该值等于A,则V=‘A’,
线程1这个时候再去判断V发现等于原始预期值A,所以就进行修改,但线程1并不能感知该值由A变成B再变成A,这就是ABA问题,如果只是数值的操作当然没问题,可如果是库存操作呢,虽然库存数没发生变化,但中间可能经过了出库和入库操作,性质已经发生了变化。
如何解决ABA问题?
增加版本号控制,因为版本号每次操作都是增加的,除了要判断V=A,还要判断版本号是否相等,则能确认是否值被修改过例子:
线程1获取该值等于A,版本号等于1,则V=‘A’,A=‘A’,Version=1,
线程2修改该值等于B,则V=‘B’,Version=2,
线程3修改该值等于A,则V=‘A’,Version=3,
线程1这个时候再去判断V发现等于原始预期值A,但是版本号不相等,因此重新获取值,V=‘A’,A=‘A’,Version=3,继续循环判断V==A 同时版本号也相等,则进行修改。
循环开销大:
由以上我们可知,V≠A,则重新获取值继续循环判断,如果线程数量特别多的话,循环开销会很大,占用cpu,短时间cpu使用率可能会增 高。