前言
目前创新互联已为近千家的企业提供了网站建设、域名、雅安服务器托管、网站运营、企业网站设计、新田网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
CAS,全称是Compare And Swap,即比较并交换,是一种乐观锁的实现。
悲观锁与乐观锁
悲观锁
总是假设最坏的情况,线程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操作
数据库中的乐观锁机制不需要借助表锁、行锁等,以修改库存为例,乐观锁实现如下:
- 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(CompareAndSwap)实现原理
URL标题:http://www.shufengxianlan.com/qtweb/news4/59254.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联