0%

Java并发编程——CAS

如果本文有错,希望在下面的留言区指正。

在之前的一些源码分析中,为了实现并发,Doug Lea 大佬在Java8及以上,大量使用了 CAS 操作。JDK 提供的关于 CAS 原子操作的类在下面工具包里面:

JDK为Java基本类型都提供了CAS工具类。

CAS

AtomicInteger 为例,进行分析:

1
2
3
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

如上面的源码,对于 CAS 操作,这里会出现3个值,expect、update、value。只有当expect和内存中的value相同时,才把value更新为update。

ABA 问题

假设如下事件序列:

线程 1 从内存位置V中取出A。
线程 2 从位置V中取出A。
线程 2 进行了一些操作,将B写入位置V。
线程 2 将A再次写入位置V。
线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。

尽管上面的CAS操作成功了,数据也没有问题,但是程序失去了对数据变换的敏感性,不知道数据的变换。

比如发生扣款/收款行为时,应当收到短信通知这个场景,
1、时刻1 : 500元
2、时刻2:转给了 A 10 元 490 元
3、时刻3:B 转入 10 元 500 元
应当收到两条短信,而不是最后我的账户余额没有变化,就一条短信都收不到

上面的图片来源不知道是谁的,只是在一个技术群里和别人聊CAS时别人发的。

解决方法

AtomicStampedReference

JDK 为了解决 ABA 问题,提供了一些方法,如 AtomicStampedReference,在版本的更新过程中,添加了一个 stamp 邮戳来标记数据的版本。

1
2
3
4
5
6
7
8
9
//比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
public boolean compareAndSet(V expectedReference, V newReference,
int expectedStamp, int newStamp)
//获得当前对象引用
public V getReference()
//获得当前时间戳
public int getStamp()
//设置当前对象引用和时间戳
public void set(V newReference, int newStamp)

具体关于 CAS 操作源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}

如上面所示,在更新的过程中,除了比较内存中value的预期值,还比较了 stamp 的预期值,只有两者都相同的时候,才会把内存中的值更新掉。

AtomicMarkableReference

AtomicMarkableReferenceAtomicStampedReference 功能相似,但AtomicMarkableReference 描述更加简单的是与否的关系。它的定义就是将状态戳简化为true|false。如下:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean compareAndSet(V       expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
客官,赏一杯coffee嘛~~~~