CAS全称Compare And Swap
,比较并交换。是一条CPU的原子指令,底层基于硬件中的汇编指令实现的。CAS算法涉及3个操作数内存值V
、预期原值A
、新值B
,当内存值V等于预期值A时,更新内存值V为新值B。
CAS示例:
例如: 对于i = 0 , i++
应用CAS思想,线程读取i的值0到内存中,预期原值A=0。
如果V等于A说明这个过程没有其他线程执行i++
操作,因此就修改V为新值B也就是1;
如果V不等于A说明有其他线程修改i的值,存在并发安全问题,放弃本次修改,重试。
2.CAS存在的问题CAS操作是原子性的,多线程并发使用CAS修改数据时,可以不用加锁。
2.1ABA问题
CAS是检查预期原值A是否发生了变化,如果预期原值A变化为另一个值C,然后又修改回A,此时线程进行CAS比较会发现预期原值A没有变化,实际上发生了变化。
发生ABA问题的本质是CAS只检查初始和最终,而不关心中间状态的变化
为什么要解决ABA问题
假设一个场景:小明账户有100元,准备取50元,在多线程并发情况下,线程A和线程B都查余额为100,线程A执行成功取出50,正常情况下线程B应该失败,但是此时如果小明的账户收款50元,那么线程B在使用CAS进行比较时也会成功,又扣减了50元,结果就是小明取了50却扣减了100元。
解决ABA问题方案
使用版本号来标识变量的状态,比如A1修改为C2再修改回A3,此时A1和A3不相等就解决了ABA问题
使用AtomicStampedReference
类来解决ABA问题
public class AtomicStampedReference{private static class Pair{final T reference; // 对象引用
final int stamp; // 用于标志版本号
private Pair(T reference, int stamp) {this.reference = reference;
this.stamp = stamp;
}
staticPairof(T reference, int stamp) {return new Pair(reference, stamp);
}
}
/ **
* ......
*/
// pair使用volatile保证可见性
private volatile Pairpair;
public boolean compareAndSet(V expectedReference, // 更新前的值
V newReference, // 更新后的值
int expectedStamp, // 预期的版本号
int newStamp) {//更新后的版本号
// 获取当前元素值 + 版本号
Paircurrent = pair;
return
expectedReference == current.reference && // 比较引用
expectedStamp == current.stamp && // 比较版本号
((newReference == current.reference && // 新引用等于旧引用
newStamp == current.stamp) || // 新版本号等于旧版本号
// 创建新的pair对象并CAS更新
casPair(current, Pair.of(newReference, newStamp)));
}
}
compareAndSet方法
执行结果:
CAS
更新过了返回true
Pair
并初始化为新值和版本号false
2.2 只能保证一个共享变量的原子操作
当对多个共享变量执行CAS操作时,就无法保证操作的原子性,比如对两个及以上变量的操作,对代码块的操作。
解决方案
Java1.5
后JDK
提供了AtomicReference
类来保证引用对象之间的原子性,可以把多个变量放在一个对象中进行原子操作。
对于代码块可以选择使用synchronized
或Lock
加锁。
2.3循环时间长开销大
CAS
一般会使用循环操作不断重试(比如自旋锁),如果时间长会增大CPU资源的开销。
优化方案
可以设定自旋的次数,默认10次。JVM
提供自适应自旋锁,自动调整CAS
循环次数,可以根据前一次相同锁CAS
执行的情况,判断CAS
次数或者不使用CAS
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧