并发编程CAS操作
简介
CAS即compare and swap,中文就是比较并交换
CAS是Java并发包的基石
原理
其实CAS的原理相对来说比较简单。将要被改变的数据和期望的值作比较,当两个值相等时,再将数值替换成新值。
其实通俗的来讲就是"==我认为原有的值是什么样子,如果一样则将原有的值更换成新值,否则不做修改,并返回原有的值=="。这一系列操作是原子的。
CAS 指的是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路
使用CAS可以实现非阻塞无锁的方式实现原子操作
实例
在java并发包中有java.util.concurrent.atomic这样一个包,该包是对Java部分数据类型的原子封装,通过CAS提供了原子性的操作方法,保证了线程安全。
如下代码
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// 使用Unsafe包来实现CAS操作private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}//使用volatile修饰符保证值value的线程可见性private volatile int value;/*** Creates a new AtomicInteger with the given initial value.** @param initialValue the initial value*/public AtomicInteger(int initialValue) {value = initialValue;}/*** Creates a new AtomicInteger with initial value {@code 0}.*/public AtomicInteger() {}/*** 获得当前值** @return the current value*/public final int get() {return value;}/*** 设置值** @param newValue the new value*/public final void set(int newValue) {value = newValue;}/*** Eventually sets to the given value.** @param newValue the new value* @since 1.6*/public final void lazySet(int newValue) {unsafe.putOrderedInt(this, valueOffset, newValue);}/*** 原子性的设置新值,返回新值** @param newValue the new value* @return the previous value*/public final int getAndSet(int newValue) {for (;;) {int current = get();if (compareAndSet(current, newValue))return current;}}/*** 比较内存中的值和预期值,如果相同则更新,否则不进行操作** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** <p>May <a href="package-summary.html#Spurious">fail spuriously</a>* and does not provide ordering guarantees, so is only rarely an* appropriate alternative to {@code compareAndSet}.** @param expect the expected value* @param update the new value* @return true if successful.*/public final boolean weakCompareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}/*** 原子性的增加1,并返回当前值.** @return the previous value*/public final int getAndIncrement() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return current;}}/*** 原子性的减少1,并返回当前值.** @return the previous value*/public final int getAndDecrement() {for (;;) {int current = get();int next = current - 1;if (compareAndSet(current, next))return current;}}/*** 原子性的增加给定的值,并返回当前值.** @param delta the value to add* @return the previous value*/public final int getAndAdd(int delta) {for (;;) {int current = get();int next = current + delta;if (compareAndSet(current, next))return current;}}/*** 原子性的增加1,并返回更新后的值.** @return the updated value*/public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}}/*** 原子性的减少1,并放回更新后的值.** @return the updated value*/public final int decrementAndGet() {for (;;) {int current = get();int next = current - 1;if (compareAndSet(current, next))return next;}}/*** 原子性的增加给定的值,并返回更新后的值.** @param delta the value to add* @return the updated value*/public final int addAndGet(int delta) {for (;;) {int current = get();int next = current + delta;if (compareAndSet(current, next))return next;}}/*** Returns the String representation of the current value.* @return the String representation of the current value.*/public String toString() {return Integer.toString(get());}public int intValue() {return get();}public long longValue() {return (long)get();}public float floatValue() {return (float)get();}public double doubleValue() {return (double)get();}}
CAS的缺点
CAS的ABA问题
问题描述
- 线程p1在共享变量中读取到值A
- p1被抢占,进程p2执行
- p2把共享变量里的值从A改成了B,再改回到A,此时被p1抢占。
- p1回来看到共享变量里的值没有被改变,于是继续执行。
- 维基百科上的例子
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了
CAS的ABA的解决办法
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作