CAS的实现原理是什么

发布时间:2021-10-20 11:22:27 作者:iii
来源:亿速云 阅读:296

本篇内容主要讲解“CAS的实现原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“CAS的实现原理是什么”吧!

悲观锁与乐观锁

悲观锁

总是假设最坏的情况,线程a每次去获取或更新数据的时候,都会觉得别的线程也正在修改这个数据,为了避免自己的更新操作丢失,线程a会尝试获取此数据的锁,线程a获取到之后,才能对此数据进行一些更新操作。在此期间,别的线程无法更新,只能等到线程a释放锁之后,才能进行更新。

之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。

悲观并发控制,实际上是一种“先取锁再访问”的保守策略。

synchronized就是对悲观锁的一种实现。

乐观锁

乐观锁假设数据一般不会造成冲突,所以在拿数据的时候不会去加锁,但是会在更新的时候判断此期间内有没有别的线程修改过数据。

CAS机制就是对乐观锁的一种实现。

乐观锁的实现——CAS

CAS操作一般包含3个参数,期望值、内存值、新值。如果期望值与内存值相等,则用新值去更新这个内存值。如果不相等,则可以再次进行比较,一直到成功为止

CAS是一种非阻塞的算法,线程在更新失败时,不需要挂起,因此省去了大量线程上下文切换的开销。

java使用Unsafe类来支持CAS操作,对Unsafe类不了解的同学可以先参考我的另外一篇文章JUC基石——Unsafe类。

我们用java代码来简要模拟CAS的过程:

/**     * @param expect 期望值     * @param update 新值     * @return     */    public int cas(int expect, int update) {        //更新失败就一直进行忙循环        while (true) {            //get方法从内存中获取最新的值            int memory = get();            if (memory == expect) {                //set方法将内存中的值设置为新值                set(update);                return update;            }        }    }

当然这只是一个模拟,实际cas操作将会用到底层的系统指令,这些指令将会保证整个cas操作具有原子性,关于这些指令,可能要另开篇幅讲解。

悲观锁的实现——synchronized

synchronized是悲观锁的典型实现,有关它的用法,可以参考我的这篇文章浅说Synchronized,早期的synchronized十分笨重,所幸在1.6之后进行了大量的优化,锁性能提升了很多,关于synchronized的优化,可以参考我的这篇文章Synchronized的优化。

CAS的缺陷——ABA问题

假设有这样的一种情况,x的内存值首先是A,线程1读取到了A,之后忙别的事情了,该值在之后被线程2改成了B,接着又被线程3改成了A,线程1此时进行CAS操作,发现内存值还是A,于是进行了更新操作。但是这个A已经不是原来的A了,或者说不是之前那个版本的A了。

解决这种缺陷,可以使用带版本号或时间戳的CAS,A值每次被更新后,版本号加1,或者更新时间戳。此时内存值与期望值相等,但却不是线程期望的版本号。

此时的A→B→A,就变成了A(version=1)→B(version=2)→A(version=3)。当使用带版本号的CAS后,就可以避免ABA问题。

CAS与synchronized适用场景

线程冲突比较小时,CAS进行自旋操作,synchronized升级为轻量级锁,也是在自旋,两者的效率差不多。

线程冲突严重时,CAS绝大部分的自旋操作将大量浪费CPU的时间片,此时synchronized升级为重量级锁,但在这种情况下,synchronized的效率远高于CAS。(因为在线程冲突严重时,synchronized已经意识到轻量级锁的自旋操作效率低下,主动升级为重量级锁,所以这里的忙循环的开销远远大于线程切换的开销)。

JAVA中的CAS操作

AtomicInteger实现了CAS,可以原子性地更新一个int类型数据,其实底层也是调用Unsafe类。但是如果要一次原子性地更新多个变量,可以使用AtomicReference,当然这个存在上述的ABA问题,这时可以使用带版本号机制的CAS实现类——AtomicStampedReference,该类使用了一个stamp字段来表示版本号,代码如下图所示:

CAS的实现原理是什么

数据库中的CAS操作

数据库中的乐观锁机制不需要借助表锁、行锁等,以修改库存为例,乐观锁实现如下:

update goods set quantity=99 where id=1 and quantity = 100;

这个情景比较简单,暂不考虑ABA问题。

以上SQL其实还是有一定的问题的,就是一旦高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。所以,需要减小乐观锁的粒度。

有一条比较好的建议,可以减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下:

update goods set quantity=quantity - 1 where id = 1 and quantity - 1 > 0

将quantity=100转化成了quantity - 1 > 0,大大减少了乐观锁的力度,效率得到很大的提升。

JVM中的CAS操作

Java调用new object()会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢?

首先,new  object()执行的时候,这个对象需要多大的空间,其实是已经确定的,因为java中的各种数据类型,占用多大的空间都是固定的。怎么去确定对象大小,可以参考我的这篇文章对象的内存布局,怎样确定对象的大小。那么接下来的工作就是在堆中找出那么一块空间用于存放这个对象。

在单线程的情况下,一般有两种分配策略:

然而,对象的创建工作是很频繁的,为了保证效率,JVM可以并发地给对象分配内存空间。由于分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

到此,相信大家对“CAS的实现原理是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

推荐阅读:
  1. CAS是什么?
  2. Java CAS底层实现原理实例详解

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

上一篇:怎么学习Python技术

下一篇:如何限制你的Python程序所能使用的内存

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》