CAS原理、缺点、AtomicInteger使用解析

cas原理#

CAS(Compare and Swap),即比较并替换, 是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

在CAS中,比较和替换是一组原子操作,不会被外部打断,且在性能上更占有优势。

Unsafe提供了三个方法用于CAS操作#

1
2
3
4
5
6

public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);

public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);

public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);

解析AtomicInteger#

当我们使用 AtomicInteger 实现多线程的加操作时,分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

public class AtomicInteger extends Number implements java.io.Serializable {

// setup to use Unsafe.compareAndSwapInt for updates

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);

​ }

​ }

private volatile int value;

public final int get() {

return value;

​ }

}

public final int getAndAdd(int delta) {

return unsafe.getAndAddInt(this, valueOffset, delta);

}

// 重点

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;

}

解析#

  1. Unsafe,是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。

  2. 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

  3. 变量value用volatile修饰,保证了多线程之间的内存可见性. 我们在使用cas更新value的时候,没有用到volatile,但get、set方法获取value时用到了volatile的可见性。这样结合来看使得AtomicInteger类获取的是主存中最新的值 且 更新时cas是原子性操作。

  4. 假设线程A和线程B同时执行getAndAdd操作, 因为比较与替换是原子性操作,所以即使在var5获取到相同的值,也会是顺序的更新。

CAS缺点#

  1. ABA问题。

    如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类 AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性, 该类提供一个引用和计数变量。

  2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

  3. 只能保证一个共享变量的原子操作

    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性

    从Java1.5开始JDK提供了 AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作

# jvm, 并发
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×