CAS概念
CAS的全称为Compare-And-Swap,翻译就是对比交换。是一条CPU的并发原语,其作用是比较工作内存值(预期值)和主物理内存的共享值是否相同,如果相同执行更新操作,否则继续比较直到主内存和工作内存的值一直为止,才做更新操作。其实现方式是靠Java语言中就是sun.misc包下的UnSafe类中的各个方法,Unsafe 方法都是由native修饰,那么在Java中这些方法是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用。
Java 中 原子类即(AtomicXXX)就是基于CAS 理念实现的。
先举一个简单的例子说明CAS的思想,后续再重点讲原子类。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
//内存值
atomicInteger.set(1);
// 预期值 | 更新值
atomicInteger.compareAndSet(1,2);
System.out.println(atomicInteger.get());
}
2
UnSafe类
是CAS的核心类,由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作依赖于UnSafe类的方法.
UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
下面我们拿AtomicInteger为例:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 变量value和volatile修饰,保证了多线程之间的可见性
private volatile int value;
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
........
}
CAS
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
CAS 问题
CAS 这种方式是乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但使用 CAS 也可能会带来几个问题:
循环时间长开销很大
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;
}
从这段代码我们可以看出如果内存中的值和预期值不一样,程序会一直循环即自旋,如果自旋长时间不成功,会给CPU带来非常大的执行开销。
所以CAS这种方式只能保证一个共享变量的原子性,当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
这里涉及到一个自旋锁概念:
在有些场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。
如果机器有多个CPU核心,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。
为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。
ABA问题
比如一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没问题的。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
👉 如果本文对你有帮助的话,欢迎点赞|在看,非常感谢
原文始发于微信公众号(阿福聊编程):聊聊CAS
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/105805.html