生活
如果你交给某人一个程序,你将折磨他一整天;如果你教某人如何编写程序,你将折磨他一辈子。——David Leinweber
CAS是什么?
昨天聊到乐观锁和悲观锁,讲到乐观锁的实现是在多线程下通过CAS不断自旋来更新数据。
那么具体什么事CAS呢,
CAS即compareAndSwap,翻译过来就是比较和交换,
确切的说是比较 旧值与预期值是否一致,如果一致,把值更新为新值,否则不更新。
JAVA中实现CAS
在JAVA里一般通过unsafe来实现cas,这个类里的compareAndSwap方法都是native方法,调用底层的c方法实现。
UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
nextOffset:内存值
cmp:预期值
val:新值
当内存值与预期值一致,即修改为新值。
CAS的应用
在JAVA里,CAS出现在AQS和原子类里。
来看下原子类里CAS的实现:
例AtomicInteger:
public final int getAndSet(int newValue) {
//自旋
for (;;) {
int current = get();
//CAS
if (compareAndSet(current, newValue))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
CAS的优缺点
优点:
非阻塞的轻量级乐观锁,在竞争不激烈的情况下,性能要优于synchronized,他有复杂的加锁解锁,还有唤醒等待,涉及到内核态和用户态的转换,性能不高。
缺点:
自旋时间过长会非常消耗CPU资源,在竞争激烈的情况下,性能反而不好。
ABA问题,一个线程获取到变量的值是A,准备把他修改成C,在修改之前,另一个线程进来把值修改成B,又修改成A,此时第一个线程在修改时,发现现在的值还是A,认为没有别的线程修改,于是修改成C成功。
如何避免ABA问题?
ABA的问题在大部分情况下(只涉及到变量本身)是没有问题的,但是也有些情况会出问题。
举个例子:
现有一个链表
A->B->C
线程1:准备把A移除,把B变成头结点。
此时
线程2:把A B 移除,在把A D放入链表 得到链表
B
A->D->C
此时线程1在执行,判断头结点是A没问题,于是
执行
A.next=null;
head=B
最后得到的链表就是
B
丢失了C
解决方式:
可以通过带版本的CAS来避免ABA问题
用AtomicStampedReference/AtomicMarkableReference解决ABA问题
AtomicStampedReference
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)));
}
AtomicMarkableReference
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)));
}
这两者的区别在于:
AtomicStampedReference 关心目标值被动过几次,
AtomicMarkableReference 关心目标值是否被动过?
关于AtomicStampedReference使用的坑
https://blog.csdn.net/xybz1993/article/details/79992120
看到别人写的。看看就好,其实不应该算AtomicStampedReference的坑,只是Integer需要注意的地方。
后记
后面研究阻塞队列和并发容器。