悲观锁和乐观锁以及CAS和Synchronized

悲观锁和乐观锁

什么是悲观锁?

​ 所谓悲观锁,总是假设最坏的情况,每次去拿数据的时候都会认为别人会修改数据,造成幻读,不可重复读,脏读等情况发生,所以每次在拿数据的时候都会上锁,共享资源每次只给一个线程使用,使得其他线程阻塞,用完后在把资源转让给其他线程,Java中synchronized和reentrant-lock等独占锁就是悲观锁思想的实现。

​ 这种锁机制,在 行锁,表锁,读锁,写锁,也用到了,都是在操作之前先上锁。

什么是乐观锁?

​ 所谓乐观锁,重视假设最好的情况,每次去拿数据都认为别人不会修改,所以不会上锁,但是会在更新时判断一下在此期间这个数据有没有更改,使用版本号机制和CAS算法实现,乐观锁适用于多读的应用类型,这样可以提高吞吐量;

​ 乐观锁有如果有冲突的话就重试,进入自旋操作,直到成功为止,乐观锁用到的机制就是CAS;

​ 数据库提供的类似于write-condition机制,就是提供的乐观锁,在java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁常见的两种实现方式

版本号机制:

​ 一般是在数据表中加一个数据版本号wersion(版本)字段,表示数据被修改的次数,当一个线程A要更新数据时, 在读取数据的同时也会读取version值,然后提交时,判断当前最后更新的version与线程A第一次取出的version版本 号是否相等,来决定是否有权利更新,更新之后version++,如果不满足条件,则不能更新,进行自旋操作,不断的重 试;

CAS算法:

​ CAS(Compare And Swap )比较与替换

​ 使用三个基本操作数:内存地址V(要操作的值),旧的预期值A(就是将要操作的值先放入A中一份,和内存 中存储的相同),要修改的新值B;

​ 工作原理:是当线程A要将一个数据的值增加1,假设内存地址V等于10,旧的预期值就是复制一份内存地址V的 值,旧的预期值A也等于10,当线程A还未提交时,线程B将这个值增加1并且提交,内存地址V的值此时为11,当线 程A进行提交时,比较内存地址V=11和旧的预期值A=10,A不等于V的实际值,提交失败;

​ 线程A会进行自旋操作,会重新获取一遍内存地址V的值,第二次,比较V和A,相等的话,线程A进行SWAP, 把地址V的值替换为新值B;

CAS机制应用:

扫描二维码关注公众号,回复: 10954301 查看本文章

​ Atomic系列类,Lock系列类的低层实现,java1.6以上版本,Synchronized转变为重量级锁之前,也会采用CAS 机制;

CAS机制缺点:

1.内存开销大,因为使用时不成功就会进入自旋状态,并发量比较高的情况下,循环往复,会给CPU带来很大 的压力。

2.不能保证代码块的原子性,CAS机制只是保证一个变量的原子性操作,不能保证整个代码块的原子性,比如需 要保证多个变量进行原子性更新,就不得不用Synchronized了。

3.ABA问题,是CAS机制的最大问题,就是使用CAS机制时,当旧的预期值A和内存中地址V相同时,不能保证 这个数据是没有没访问且修改过的,只是值没变,这就是ABA问题;

​ 不过在JDK1.5之后AtomicStampedReference类就对此问题进行了解决,因为这个类内部,AtomicReferencele 类中的CAS机制多了一个时间戳,当更新时还必须更新时间戳,只有在预期值A和内存地址V还有时间戳都满足相等 条件时,写入才会成功,因此即使出现ABA问题,对象被反复读写,然后写回原值,只要时间戳发生变化,就能防止 不恰当的写入。

乐观锁和悲观锁的使用场景

​ 乐观锁适用于写比较少的情况下(多读场景),既冲突很少发生的情况下,节省锁的开销,加大了系统的整个吞吐量。

​ 悲观锁适用于多写的情况下,既经常发生冲突,如果使用乐观锁,如果发生冲突,提交失败,会持续进行自旋,占用了大量的内存,所有已一般多写的场景下用悲观锁比较合适。

CAS和synchronized

什么是Synchronized同步锁?

​ synchronized同步锁,使得操作变成了原子性操作,实现了线程安全,Synchronized关键字会让没有的到锁资源的线程进入BLOCKED(阻塞)状态,而后争夺到锁资源后恢复RUNNABLE状态,过程涉及操作系统用户模式和内核模式转换,代价高;

​ java1.6以上版本,Synchronized转变为重量级锁之前,也会采用CAS机制;

​ jdk1.6之后,对Synchronized锁做了优化,适应自旋锁(自旋锁:为了减少线程状态改变带来的消耗,不停地执行当前操作)、锁消除(把不可能存在共享数据竞争的锁尽心消除)、锁粗化(将连续加锁,精简到只加一次锁)、轻量级锁(无竞争条件下,通过CAS消除同步互斥)、偏向锁(无竞争条件下,消除整个同步互斥,连CAS都不操作)。

synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但是获得了高吞吐量,在线程冲突较少的情况下,可以获得和CAS类似的性能,线程冲突严重的情况下,性能远高于CAS。

原子操作类替换同步锁

​ 在java.util.concurrent.atomic包下,以Atomic开头的包装类,例如AtomicBoolean、AtomicInteger就是分别用于boolean和integer类型的原子性操作,使用之后,能够达到Synchronized同步锁的效果,在某些情况下,代码的性能会比Synchronized更好,Atomic操作类的底层就是利用了乐观锁的CAS机制;

CAS和synchronized的使用场景

​ 资源竞争少的情况下,synchronized同步锁是进行线程阻塞和唤醒切换,以及用户状态和内核状态之间的切换操作,额外消耗CPU,而CAS是基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此性能更高。

​ 资源竞争严重的情况下,CAS自旋的概率比较大,浪费CPU资源,效率低于synchronized。

发布了5 篇原创文章 · 获赞 4 · 访问量 18

猜你喜欢

转载自blog.csdn.net/qq_45218334/article/details/105618508