一、CAS原理:
CAS的全程即Compare And Swap,翻译成中文为比较并交换;
CAS操作依赖于CPU指令CMPXCHG来实现比较并交换操作的原子性,通过查看HotSpot源码如下:
可以看到这个实现跟CPU的类型相关,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
二、AtomicInteger源码分析:
以AtomicInteger类的实现源码分析,其他原子操作类的代码类似;
AtomicInteger封装了对int类型的原子操作,通过类的接口操作int数据都是原子性的,不会造成在多线程环境中脏数据的读写;实现int的原子操作是通过unsafe接口函数操作完成的(比如putOrderedInt、compareAndSwapInt等),unsafe接口函数都是本地函数;
在AtomicInteger类中定义了一个int类型的变量:
接口函数:
getAndSet:
调用compareAndSet比较current和value值是否相等,如果相等就设置为newValue并返回True,否则返回False;
如果compareAndSet返回false,则说明value值已经是脏数据(被其他线程修改过),因此会一直循环下去,直至compareAndSet返回True则返回;
getAndIncrement:
与上面的类似,如果compareAndSet返回false,则会一致循环下去,直至compareAndSet返回True则返回;
同样的效果也能通过锁来达到,但是原子操作的开销比锁的开销小很多;
incrementAndGet:
与上面的类似,如果compareAndSet返回false,则会一致循环下去,直至compareAndSet返回True则返回;
compareAndSet调用了unsafe.compareAndSwapInt,unsafe.compareAndSwapInt是本地方法,调用操作系统原生程序;通过查看jdk源码发现其实是调用了CMPXCHG指令;
由于AtomicInteger采用了无限循环的方式来操作,因此称作自旋CAS方式;CAS方式存在三大问题:
1、ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
public boolean compareAndSet (V expectedReference,//预期引用
V newReference,//更新后的引用
int expectedStamp, //预期标志
int newStamp) //更新后的标志
2、循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3、只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。