简析CAS机制与实现原理

在学习CAS的过程中,我百思不得其解的一个问题就是在多cpu并发的环境下,CAS如何保证线程的安全性呢?关于这个问题下面的两篇博客写的比较不错,基本把其中的原理解释清楚了,这里我只作一个简单的阐述。

http://m.blog.csdn.net/wbb_1216/article/details/62882921

http://m.blog.sina.com.cn/s/blog_ee34aa660102wsuv.html#page=7

一.锁机制的缺点

      在jdk5以前,java基本只有靠synchronized关键字来保证同步,这会导致需要同步的对象被加上独占锁,也就是我们常说的悲观锁。悲观锁存在一些问题,典型的如:

1.在多线程竞争下,加锁和释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

2.一个线程持有锁会导致其他所有需要此锁的线程挂起

      与悲观锁对应的是乐观锁,乐观锁在处理某些场景的时候有更好的表现,所谓乐观锁就是认为在并发场景下大部分时候都不会产生冲突,因此每次读写数据读不加锁而是假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS。

二.什么是CAS

         CAS操作包含三个操作数——内存位置(V),预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器将会自动将该位置值更新为新值,否则,不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。

通过以上定义我们知道CAS其实是有三个步骤的

1.读取内存中的值

2.将读取的值和预期的值比较

3.如果比较的结果符合预期,则写入新值

        现在的CPU都支持“读-比较-修改”原子操作,也就是一个cpu在执行这个操作的时候,是绝对不会被其他线程中断的,但是多cpu环境就不一定了。可能在cpu1执行完比较操作准备修改的时候,另一块cpu2火速完成了一次“读-比较-修改”操作从而让内存中的值发生变化。而此时cpu1再写入明显就不对了。并且这个场景也并没有违背该命令的原子性。

        解决这个问题的答案其实也很简单,那就是就是volatile我之前的一篇博客讨论了volitile变量的写入机制,volatile的官方解释是可以保证可见性、顺序性、一致性。下面简单解读一下这三个特性:

可见性:volatile修饰的对象在加载时会告知JVM,对象在cpu的缓存上对多个线程是同时可见的。

顺序性:这里有JVM内存屏障的概念,可以简单理解为:可以保证线程操作对象时是顺序执行的不会进行指令重排序

一致性:可以保证多个线程读取数据时,读到的数据是最新的。

        在上面的场景中,解决问题的关键就是volatile的一致性,volitile的写操作是安全的,因为他在写入的时候lock声言会锁住cpu总线导致其他CPU不能访问内存(现在多用缓存一致性协议,处理器嗅探总线上传播的数据来判断自己缓存的值是否过期),所以,写入的时候若其他cpu修改了内存值,那么写入会失败。上面的问题中,由于cpu1CAS指令执行一半的时候cpu2火速修改了变量的值,因此这就让该变量在所有cpu上的缓存失效,cpu1在进行写入操作的时候,也会发现自己的缓存失效,那么CAS操作就会失败(在javaautomicinteger中,会不停的CAS直到成功)。所以即使是在多cpu多线程环境下,CAS机制依然能够保证线程的安全。

        但是CAS也并非完美的,CAS存在3个问题,ABA问题,循环时间长的话开销大(也就是说多冲突环境下乐观锁的重试消耗大),以及只能保证一个共享变量的原子操作,本文就不再详细讨论了。

你可能感兴趣的:(java并发编程)