Java中的CAS
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步,java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
基本介绍
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
使用场景
有的时候我们需要对变量进行操作,如果是多线程,则有可能达不到我们的预期结果,Synchronized
等关键字,可以解决问题,但是Synchronized
关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。这个时候我们就可以使用Atomic的一些原子操作来进行。
public class CASTest {
public static volatile int count = 0;
public static AtomicInteger atomicCount = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
count++;
atomicCount.incrementAndGet();
}
System.out.println("count++ end!");
}).start();
}
Thread.sleep(2000);
System.out.println(count);
System.out.println(atomicCount.get());
}
}
来看一下这个的代码输出
count++ end!
count++ end!
18003
20000
可以看到volatile也没有办法保证运算的原子性。AtomicInteger使用CAS操作,可以保证运算的原子性。
AtomicInteger的实现原理
我们可以看一下AtomicInteger
中incrementAndGet()的源码
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
其是通过Unsafe来实现的,我们进入Unsafe中的getAndInt()方法中
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;
}
compareAndSwapInt是本地的方法,是通过CPU的cmpxchg指令,去比较寄存器中的 A 和 内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。
CAS存在的问题
-
ABA问题CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。JDK从1.5开始提供了
AtomicStampedReference
类来解决ABA问题。 -
并发高循环时间长的时候开销大CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。
-
只能保证一个共享变量的原子操作对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始JDK提供了
AtomicReference
类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
参考
https://tech.meituan.com/2018/11/15/java-lock.html
关注公众号:蜜蜂技术巢了解更多知识
网友评论