CAS
什么是CAS
CAS的全称是Compare-And-Swap,直译就是对比与交换。CAS是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。
简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换
CAS使用举例
在并发线程操作中,比如并发线程累加操作同一个变量500次,但最终的结果一般都到不了500,原因是并发状态下的线程不安全问题
针对这个问题,我们可以将共享的操作变量int换成换成AtomicInteger,这样就能解决问题,其中AtomicInteger实现是使用了CAS操作
CAS问题
CAS方式为乐观锁,synchronized为悲观锁。因此使用CAS解决并发问题通常情况下性能更优
但是CAS仍然存在三大问题:ABA问题、循环开销大、只能保证一个共享变量的原子操作
ABA问题
什么是ABA问题:因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,中途变成了B,后来又变成A,那么使用CAS进行检查时会发现它的值没有发生变化,而实际却发生过变化。在大多数的业务场景ABA问题是可以容忍的,但是部分业务就会带来问题。
如:小明去银行取款,银行账户有100元,小明准备取50出来,由于机器不灵,小明多点击了几次取款操作,触发了后台线程A和线程B的工作,此时线程A操作成功(100->50),线程B阻塞。此时正好小明收到了50元的转账且执行成功了(50->100),之后线程B执行了,又执行成功(100->50)。最终小明账户只剩下50元,等于被错误的扣了两次款。
ABA问题的解决思路:使用版本号。再变量前面追加上版本号,每次变量更新的时候吧版本号+1,那么A->B->A就会变成1A->2B->3A。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设为给定的更新值
循环时间长,开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁
原子操作类
原子更新基本类型类
使用原子的方法更新基本类型,Atomic包提供以下3个类
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
以上三个类提供的方法几乎一模一样,下面以AtomicInteger为例
- public final int get():获取当前的值
- public final int getAndSet(int newValue):获取当前的值,并设置新的值
- public final int getAndIncrement():获取当前的值,并自增1
- public final int getAndDecrement():获取当前的值,并自减1
- public final int getAndAdd(int delta):获取当前的值,并加上预期的值
- void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能会导致其他线程在之后的一段时间内还是可以读到旧值
源码解析:AtomicInteger:getAndIncrement():
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
}
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
public final boolean weakCompareAndSetInt(Object o, long offset,
int expected,
int x) {
return compareAndSetInt(o, offset, expected, x);
}
分析:可以看到,getAndIncrement操作的自增1操作,最终还是调用的unsafe类提供的compareAndSetInt(CAS)实现的
原子更新数组
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的3个类:
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素
- AtomicReferenceArray:原子更新引用类型数组里的元素
这三个类的最常用方法是如下两个方法:
get(int index):获取索引为index1的元素值
compareAndSet(int i,E except,E update):如果当前值等于预期值,则以原子方式将数组位置 i 的元素设为update值

原子更新引用类型
Atomic包提供了以下三个类:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型
这三个类提供的方法都差不多,首先构造一个引用对象,然后把引用对象set进Atomic类,然后调用compareAndSet等一些方法去进行原子操作,原理都是基于Unsafe实现,但AtomicReferenceFieldUpdater略有不同,更新的字段必须用volatile修饰
原子更新字段类
如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下三个类进行原子字段更新
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值和引用关联起来,可用于原子地更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题
要想原子地更新字段需要两部,第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二部,更新类的字段(属性)必须使用public volatile修饰符。