悲观锁和乐观锁

悲观锁和乐观锁

引用地址:

https://blog.csdn.net/qq_34337272/article/details/81072874

一、乐观锁和悲观锁的概念

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)synchronized和Reentratlock就是悲观锁思想的实现。

乐观锁:总是假设最好的情况,每次去那数据的时候都会认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。atomic包下的原子变量类就是使用了乐观锁一种方式CAS实现的

两种锁使用场景:

没有搞下之分,要看场景,多读的场景下使用乐观锁,因为这种情况修改少,可以省去锁的开销,加大系统的吞吐量。多写的情况下,就应该使用悲观锁,因为这种情况下冲突情况比较多。

二、两种锁的实现机制

2.1、乐观锁的实现机制

乐观锁一般使用版本号机制和CAS算法实现

1、版本号机制

一般是在数据库表中加上一个数据版本号version字段,表示数据被修改的次数,更新时会比对自己在读取数据时获取到的版本号与当前数据库中的版本号比对,相等时更新,不相等就重现操作,直到成功。

2、CAS算法:

即compare and swap(比较与交换),是一种有名的无锁算法。

无锁编程:即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。

CAS算法涉及到三个操作数据:

变量所对应的内存地址V,里面也记录着A

旧的变量值A

拟写入的新值B

当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作,即不会被打断的操作,比如a=a+1)一般情况下是一个自旋操作,即不断的重试

乐观锁的缺点:

1、ABA问题

第一步是V,第二步是A,第三步是B,当主线程走到B的时候,另一个线程将A短暂的修改成D,然后在修改回A,这个时候,主线程进行更新,发现A和V是相等的,他就会认为A没有被修改过,这就是ABA问题,这种问题的危害是,完全可以根据A是D的时候,写个代码完成某件事情。

2、循环时间长开销大

CAS的自旋操作,如果长时间不成功,会给CPU带来非常大的执行开销。

JVM的pause指令科技提升一定的效率

pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3、只能保证一个共享变量的原子操作

CAS只对单个共享变量有效,当涉及多个共享变量时CAS无效。JDK1.5开始提供了AtomicReference类,来保证引用对象之间的原子性,可以把多个变量放在一个对象里进行CAS操作。

 三、CAS与synchronized的使用情景

简单来说,在写比较少的情况下,使用乐观锁,比如多读场景,冲突比较少。synchronized适用于写比较多的场景,冲突一般比较多

1、对于线程冲突较轻的情况下,synchronized锁会将线程阻塞和唤醒切换,这样比较浪费CPU资源。因为synchronized是基于JVM实现的。而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、线程 冲突严重的情况下,CAS自旋的概率比较大,从而浪费大量的CPU资源

原文地址:https://www.cnblogs.com/gushiye/p/13847247.html