CAS操做能够分为如下三个步骤:java
步骤 1.读旧值(即从系统内存中读取所要使用的变量的值,例如:读取变量i的值)算法
步骤2.求新值(即对从内存中读取的值进行操做,可是操做后不修改内存中变量的值,例如:i=i+1,这一步只进行 i+1,没有赋值,不对内存中的i进行修改)多线程
步骤3.两个不可分割的原子操做并发
第一步:比较内存中变量如今的值与 最开始读的旧值是否相同(即从内存中从新读取i的值,与一开始读取的 i进行比较)框架
第二步:若是这两个值相同的话,则将求得的新值写入内存中(即:i=i+1,更改内存中的i的值)测试
若是这两个值不相同的话,则重复步骤1开始this
注:这两个操做是不可分割的原子操做,必须两个同时完成atom
CAS是单词compare and set的缩写,意思是指在set以前先比较该值有没有变化,只有在没变的状况下才对其赋值。spa
咱们经常作这样的操做.net
if(a==b) { a++; }
试想一下若是在作a++以前a的值被改变了怎么办?a++还执行吗?出现该问题的缘由是在多线程环境下,a的值处于一种不定的状态。采用锁能够解决此类问题,但CAS也能够解决,并且能够不加锁。
int expect = a; if(a.compareAndSet(expect,a+1)) { doSomeThing1(); } else { doSomeThing2(); }
这样若是a的值被改变了a++就不会被执行。
按照上面的写法,a!=expect以后,a++就不会被执行,若是咱们仍是想执行a++操做怎么办,不要紧,能够采用while循环
while(true) { int expect = a; if (a.compareAndSet(expect, a + 1)) { doSomeThing1(); return; } else { doSomeThing2(); } }
采用上面的写法,在没有锁的状况下实现了a++操做,这其实是一种非阻塞算法。
Java.util.concurrent.atomic包中几乎大部分类都采用了CAS操做,以AtomicInteger为例,看看它几个主要方法的实现:
public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } }
getAndSet方法JDK文档中的解释是:以原子方式设置为给定值,并返回旧值。原子方式体如今何处,就体如今compareAndSet上,看看compareAndSet是如何实现的:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
不出所料,它就是采用的Unsafe类的CAS操做完成的。
再来看看a++操做是如何实现的:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
几乎和最开始的实例如出一辙,也是采用CAS操做来实现自增操做的。
++a操做和a++操做相似,只不过返回结果不一样罢了
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
此外,java.util.concurrent.ConcurrentLinkedQueue类全是采用的非阻塞算法,里面没有使用任何锁,全是基于CAS操做实现的。CAS操做能够说是JAVA并发框架的基础,整个框架的设计都是基于CAS操做的。
一、ABA问题
CAS操做容易致使ABA问题,也就是在作a++之间,a可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为a的值没有变。a在外面逛了一圈回来,你能保证它没有作任何坏事,不能!!也许它讨闲,把b的值减了一下,把c的值加了一下等等,更有甚者若是a是一个对象,这个对象有多是新建立出来的,a是一个引用呢状况又如何,因此这里面仍是存在着不少问题的,解决ABA问题的方法有不少,能够考虑增长一个修改计数,只有修改计数不变的且a值不变的状况下才作a++,也能够考虑引入版本号,当版本号相同时才作a++操做等,这和事务原子性处理有点相似!
二、比较花费CPU资源,即便没有任何争用也会作一些无用功。
三、会增长程序测试的复杂度,稍不注意就会出现问题。
能够用CAS在无锁的状况下实现原子操做,但要明确应用场合,很是简单的操做且又不想引入锁能够考虑使用CAS操做,当想要非阻塞地完成某一操做也能够考虑CAS。不推荐在复杂操做中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。