CAS简介
锁的开销极大。在某些场景,如保证一个变量的 read-modify-write操作的原子性。这种场景可以通过使用CAS解决而不需要用到锁。
CAS,Compare and swap 比较并交换,是一种乐观锁的实现方式。是一个中由处理器保证原子性的if-then-act操作。它通过提供一个变量内存位置,预期值(旧值)和新值。将预期值和变量的当前值进行比较,如果相等即证明变量并没有被改变,将该变量修改成新值。如果不相等则进行重试(预期值会重新加载),直到成功。
Unsafe类中通过CAS修改int类型变量源码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
*var1 AtomicInteger对象
*var2 内存偏移量
*var4 增加的值
*var5 获取的变量原值,保存在var5用于当预期值
*compareAndSwapInt会比较内存值和var5相等的话就会改变内存值(即AtomicIntege * r对象中的变量)。
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取内存的当前值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
原子变量
以上代码是原子遍历AtomicInteger自增代码的实现片段。JDK基于CAS提供了保证共享变量read-modify-write操作原子性的类。
分组 | 类名 |
---|---|
基本类型 | AtomicInteger,AtomicLong,AtomicBoolean |
数组类型 | AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray |
字段更新 | AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater |
引用型 | AtomicReference,AtomicStampedReference,AtomicMarkableReference |
AtomicInteger
方法 | 作用 |
---|---|
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) | 使用将给定函数应用于当前值和给定值的结果原子更新当前值,返回更新后的值。 |
int addAndGet(int delta) | 将给定的值原子地添加到当前值。 |
boolean compareAndSet(int expect, int update) | 如果当前值 ==为预期值,则将该值原子设置为给定的更新值。 |
int decrementAndGet() | 原子减1当前值。 |
double doubleValue() | 返回此值 AtomicInteger为 double一个宽元转换后。 |
float floatValue() | 返回此值 AtomicInteger为 float一个宽元转换后。 |
int get() | 获取当前值。 |
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) | 使用给定函数应用给当前值和给定值的结果原子更新当前值,返回上一个值。 |
int getAndAdd(int delta) | 将给定的值原子地添加到当前值。 |
int getAndDecrement() | 原子减1当前值。 |
int getAndIncrement() | 原子上增加一个当前值。 |
int getAndSet(int newValue) | 将原子设置为给定值并返回旧值。 |
int getAndUpdate(IntUnaryOperator updateFunction) | 用应用给定函数的结果原子更新当前值,返回上一个值。 |
int incrementAndGet() | 原子上增加一个当前值。 |
int intValue() | 将 AtomicInteger的值作为 int 。 |
void lazySet(int newValue) | 最终设定为给定值。 |
long longValue() | 返回此值 AtomicInteger为 long一个宽元转换后。 |
void set(int newValue) | 设置为给定值。 |
String toString() | 返回当前值的String表示形式。 |
int updateAndGet(IntUnaryOperator updateFunction) | 使用给定函数的结果原子更新当前值,返回更新的值。 |
boolean weakCompareAndSet(int expect, int update) | 如果当前值 ==为预期值,则将值设置为给定更新值。 |
ABA问题
以上说到,CAS是将预期值和内存当前值比较,通过比较结果来判断其他线程是否修改过该变量。但是如果存在其他线程修改变量后又改回原值(即预期值),在某些场景就会存在问题。
ABA问题例子
银行账户 500元(共享变量)
- 要取出50元,机器故障发送了2个请求A,B,此时两个请求的期望值都是500,新值450
- A请求执行完后,内存值变成450。所以第二个请求是不会成功的。
- 但是如果在B请求执行前,C又往账户存了50块。这时银行帐号变成500。B请求预期值满足提交成功,银行账户最终存款为 450。
这种情况下,存款少了50块。
ABA解决方案。
ABA问题可以通过版本号来解决,每次修改操作都添加一个版本号。例如刚才的取款操作加个版本号 1,在存款操作执行后版本号+1,变为2。取款的第二次请求执行时就会判断版本号不是1,执行失败。
原子变量AtomicStampedReference,AtomicMarkableReference 中处理了ABA问题。
注意
- CAS只能保证一个共享变量的操作的原子性(原子性操作+原子性操作≠原子操作),如果要保持多个共享变量的操作的原子性,就必须使用锁。
- 如果变量更新多次失败,循环时间长开销大。
- ABA问题