您的位置:首页 > 其它

CAS原理

2020-04-23 10:05 1066 查看

CAS是什么?

CAS是比较并交换的意思,其全称为Compare-And-Swap,简称为CAS。
实际上指的是原子类

AtomicReference
中的
compareAndSet(V expect, V update)
这个方法。
其源码为:

/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

expect
指的是主内存的期望值,
update
为主内存的更新值。
当工作内存将更新值写回到主内存的时候,会将主内存的值跟期望值进行比较,当主内存的值与期望值一致时,才会将主内存的值更新为更新值;否则不会更新,工作内存会重新获取主内存的值。

Unsafe类:

通过源码可以看出

compareAndSet(V expect, V update)
这个方法的实现调用的是
unsafe
类的
compareAndSwapObject(this, valueOffset, expect, update)
方法。

this
参数指的是当前对象。
valueOffset
参数表示该变量值在内存中的偏移地址,即可以根据内存地址操作变量值。
expect
参数是主内存的期望值。
update
参数是需要更新的值。

其实

unsafe
类就是根据内存偏移地址获取数据的。
当点击进入
compareAndSwapObject
这个方法可以看到的源码:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

底层是一个

native
方法,调用的是C语言代码。

从上面源码可以看出

unsafe
类是实现CAS的核心类,由于Java方法无法直接访问底层系统,所以需要通过本地(native)方法来访问。
unsafe
相当于一个后门,基于该类可以直接操作特定内存的数据。
unsafe
类存在于
sun.misc
包中,其内部方法操作可以像C的指针一样直接操作内存,Java中的CAS操作是依赖于
unsafe
类的方法。

CAS的底层原理:

CAS是一条CPU的并发原语。
其功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在Java语言中就是

sun.misc.Unsafe
类中的各个方法。
调用
Unsafe
类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖与硬件的功能,通过它实现了原子操作

由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题

分析AtomicInteger类中的自增方法

AtomicInteger
类中的
getAndIncrement()
方法:

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() {
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

Unsafe
类中的
getAndAddInt
方法:

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

首先明确

getAndAddInt
方法中几个参数的含义:
Object var1
是传入操作对象,即
AtomicInteger
这个类的对象
long var2
AtomicInteger
对象的内存地址偏移量
int var4
是本次执行的增量
int var5
是从主内存中读取的期望值,用于写会主内存时比较使用

执行过程:
首先从主内存中获取期望值,然后执行CAS过程。如果执行失败,则继续从主内存中获取期望值,在执行CAS过程,直到CAS执行成功,返回结果。

CAS的缺点:

  1. 循环时间长,开销大。如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
  2. 只能保证一个共享变量的原子操作。对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就需要用锁来保证原子性。
  3. 会引入ABA问题

原子引用:

原子引用类指的是

AtomicReference<V>
,只需要指定一个类,该类即可称为原子类。

/**
* Creates a new AtomicReference with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicReference(V initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicReference with null initial value.
*/
public AtomicReference() {
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(V newValue) {
value = newValue;
}

使用构造方法或者

set(V newValue)
,可以使实例化的类具有原子特性。

CAS的ABA问题:

原子类

AtomicInteger
的ABA问题:
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并交换,那么在这个时间差会导致数据的变化。

比如说一个线程1从内存中取出变量的值是A,这时另一个线程2也从内存中取出这个变量的值是A,并且线程2进行了一些操作将这个变量的值变成了B,然后线程2又将这个变量的值修改为A,这时候线程1进行CAS操作发现内存中仍然是A,然后线程1操作成功。
尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

CAS的ABA问题解决:

使用

AtomicStampedReference
类解决
其主要思路是添加一个版本号来控制执行CAS变量的值是否修改过。

AtomicStampedReference源码:

/**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}

initialRef
为初始化值,
initialStamp
为初始化版本号。

获取当前版本号:

/**
* Returns the current value of the stamp.
*
* @return the current value of the stamp
*/
public int getStamp() {
return pair.stamp;
}

获取当前变量的值:

/**
* Returns the current value of the reference.
*
* @return the current value of the reference
*/
public V getReference() {
return pair.reference;
}

执行CAS的源码:

/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
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)));
}

该方法有四个参数:

expectedReference
期望的变量值
newReference
要修改的变量值
expectedStamp
期望的版本号
newStamp
要修改的版本号

  • 点赞
  • 收藏
  • 分享
  • 文章举报
yxw678 发布了6 篇原创文章 · 获赞 0 · 访问量 325 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: