线程同步-CAS与原子变量

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元(共享变量)

  1. 要取出50元,机器故障发送了2个请求A,B,此时两个请求的期望值都是500,新值450
  2. A请求执行完后,内存值变成450。所以第二个请求是不会成功的。
  3. 但是如果在B请求执行前,C又往账户存了50块。这时银行帐号变成500。B请求预期值满足提交成功,银行账户最终存款为 450。

这种情况下,存款少了50块。

ABA解决方案。

ABA问题可以通过版本号来解决,每次修改操作都添加一个版本号。例如刚才的取款操作加个版本号 1,在存款操作执行后版本号+1,变为2。取款的第二次请求执行时就会判断版本号不是1,执行失败。

原子变量AtomicStampedReference,AtomicMarkableReference 中处理了ABA问题。

注意

  1. CAS只能保证一个共享变量的操作的原子性(原子性操作+原子性操作≠原子操作),如果要保持多个共享变量的操作的原子性,就必须使用锁。
  2. 如果变量更新多次失败,循环时间长开销大。
  3. ABA问题