CAS机制

作者: 繁星追逐 | 来源:发表于2019-07-29 20:11 被阅读0次
线程同步

加了同步锁之后,count自增的操作变成了原子性操作,所以最终输出一定是count=200,代码实现了线程安全。虽然synchronized确保了线程安全,但是在某些情况下,这并不是一个最有的选择。

关键在于性能问题。

synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。

尽管JAVA 1.6为synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过过度,但是在最终转变为重量级锁之后,性能仍然比较低。所以面对这种情况,我们就可以使用java中的“原子操作类”。

所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。如AtomicBoolean,AtomicUInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。

现在我们尝试使用AtomicInteger类:

原子操作

使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在某些情况下,代码的性能会比synchronized更好。

而Atomic操作类的底层正是用到了“CAS机制”。

CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换。

CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

我们看一个例子:

1. 在内存地址V当中,存储着值为10的变量。

2. 此时线程1想把变量的值增加1.对线程1来说,旧的预期值A=10,要修改的新值B=11.

3. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

4. 线程1开始提交更新,首先进行A和地址V的实际值比较,发现A不等于V的实际值,提交失败。

5. 线程1 重新获取内存地址V的当前值,并重新计算想要修改的值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

6. 这一次比较幸运,没有其他线程改变地址V的值。线程1进行比较,发现A和地址V的实际值是相等的。

7. 线程1进行交换,把地址V的值替换为B,也就是12.

从思想上来说,synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。

在java中除了上面提到的Atomic系列类,以及Lock系列类夺得底层实现,甚至在JAVA1.6以上版本,synchronized转变为重量级锁之前,也会采用CAS机制。

CAS的缺点:

1) CPU开销过大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

2) 不能保证代码块的原子性

CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

3) ABA问题

这是CAS机制最大的问题所在。(后面有介绍)

在Java中,AtomicStampedReference类就实现了用版本号作比较额CAS机制。

我们现在来说什么是ABA问题。假设内存中有一个值为A的变量,存储在地址V中。

此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差。线程1和线程2已经获取当前值,线程3还未获取当前值。

接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因为某种原因被阻塞住,没有做更新操作;线程3在线程1更新之后,获取了当前值B。

在之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A。

最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值A”,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量值A更新成了B。

看起来这个例子没啥问题,但如果结合实际,就可以发现它的问题所在。

我们假设一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

由于提款机硬件出了点问题,小灰的提款操作被同时提交了两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

理想情况下,应该一个线程更新成功,一个线程更新失败,小灰的存款值被扣一次。

线程1首先执行成功,把余额从100改成50.线程2因为某种原因阻塞。这时,小灰的妈妈刚好给小灰汇款50元。

线程2仍然是阻塞状态,线程3执行成功,把余额从50改成了100。

线程2恢复运行,由于阻塞之前获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以会成功把变量值100更新成50。

原本线程2应当提交失败,小灰的正确余额应该保持100元,结果由于ABA问题提交成功了。

1. java语言CAS底层如何实现?

利用unsafe提供的原子性操作方法。

2.什么事ABA问题?怎么解决?

当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。

利用版本号比较可以有效解决ABA问题。

相关文章

  • 6、多线程

    synchronized关键字与CAS机制 synchronized:悲观锁CAS 机制:乐观锁当程序的并发量比较...

  • [转]Java中的CAS操作以及锁机制详解

    原文: Dyson~ - Java中的CAS操作以及锁机制详解 1. 关于CAS操作 CAS:Compare An...

  • (转载)Java CAS 和 synchronized 和 Lo

    原文链接:Java CAS 和 synchronized 和 Lock - CSDN博客 CAS 机制 适用场景:...

  • CAS知识摘抄(摘自:程序员小灰)

    什么是CAS? CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。 CAS机制当中使...

  • CAS机制

    还有就是这个atomic包,使用可以达到synchronized的效果,实现原理就是CAS了。 CAS也叫自旋锁,...

  • CAS机制

    加了同步锁之后,count自增的操作变成了原子性操作,所以最终输出一定是count=200,代码实现了线程安全。虽...

  • CAS机制

    CAS(Compare-And-Swap)算法保证数据操作的原子性。 CAS 算法是硬件对于并发操作共享数据的支持...

  • CAS机制

    当两个线程同时操作一个变量时,会拿到当前变量的值 如A = 0; A++此时,预期旧值则是A = 0 新值=1对...

  • CAS机制

    一、为什么需要CAS机制? 为什么需要CAS机制呢?我们先从一个错误现象谈起。我们经常使用volatile关键字修...

  • CAS机制

    原子操作 原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。通俗讲就是你...

网友评论

      本文标题:CAS机制

      本文链接:https://www.haomeiwen.com/subject/bevirctx.html