持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
CAS
什么是CAS
cas全称Compare-And-Swap,直译对比交换。是一条CPU的原子指令,其方法是让CPU必将两个值是否相等,然后原子的更新某个位置的值,实现方式是靠硬件实现的。
cas操作是原子性的,所以多线程并发使用cas更新数据时,可以不使用锁。
cas使用示例
在java中,封装了Atomic*开头的类。
举一个简单的例子 AtomicInteger
public class CasDemo {
private AtomicInteger i = new AtomicInteger(0);
public int add(){
return i.addAndGet(1);
}
}
如果不使用cas进行操作的话,为了保证多线程中不会出现并发问题,就需要进行加锁处理
public class CasDemo {
private int i=0;
public synchronized int add(){
return i++;
}
}
cas问题
ABA问题
因为cas在操作值的时候,会先检查值是否发生变化,如果没有发生变化,则将本次修改的值更新,如果有变化,会获取最近的值在进行比较,从而更新值,这样就会产生一个问题,假设第一个A线程把数值a从1更新到了2,但是此时另一个线程进来了,A线程并没有结束,B线程获取到最新的值为a=2,但是B线程把a又改为1,因为更新完的值对所有线程都具有可见性,所以A线程在读取的时候又会读到a=1,此时就产生了ABA问题。
解决办法:在jdk1.5之后,在Atomic包中引入了一个AtomicStampedReference来解决ABA问题
循环时间长开销大
当cas比较两个值不成功时,会陷入到循环取值比较,但是如果长时间自旋不成功的时候,会给cpu带来非常大的执行开销,甚至可能进入一个无限循环
解决方案:jdk1.8之后引入的LongAddr来解决
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
解决方案:jdk1.5之后提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作
UnSafe
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。
java中Atomic*类中的底层主要就是通过unsafe操作的
AtomicInteger
原子类以AtomicInteger为例
常用api如下
public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值
源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//用于获取value字段相对当前对象的“起始地址”的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
/**
* Gets the current value.
*
* @return the current value
*/
//返回当前值
public final int get() {
return value;
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(int newValue) {
value = newValue;
}
......
}
AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。
而value使用volatile进行修饰的,这样的话,就可以保证多线程之间的可见性
AtomicStampedReference
由于一般的Atomic*会产生ABA问题,所以jdk1.5之后,在Atomic包中引入了一个AtomicStampedReference来解决ABA问题,来看源码
public class AtomicStampedReference<V> {
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);
}
}
private volatile Pair<V> pair;
....
/**
* expectedReference :更新之前的原始值
* newReference : 将要更新的新值
* expectedStamp : 期待更新的标志版本
* newStamp : 将要更新的标志版本
*/
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) ||
// 构造新的Pair对象并CAS更新
casPair(current, Pair.of(newReference, newStamp)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
// 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
防止ABA问题的产生,在AtomicStampedReference类中添加了一个版本号的概念,会判断如果元素值和版本号都没有变化,并且和新的也相同,返回true,如果元素值和版本号都没有变化,并且和新的不完全相同,就构造一个新的Pair对象并执行CAS更新pair。