1.什么是CAS?
CAS:Conmpare And Swap =比较和交换
在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子
指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置
的内容修改为新的给定值。
java中,在JDK5之前java语言是靠synchronized关键字保证同步,导致存在以下几个问题:
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级导致,引起性能风险。
synchronized是一种悲观锁,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁就是每次不加锁而是假设没有冲突而且完成某项操作,如果因为冲突失败就重试,
直到成功为止。乐观锁用到的机制就是CAS。
2.CAS主要代码是放在JUC的atomic包下
3.CAS的使用
package com.ning.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 16790
* CAS操作
*/
public class CASDemo {
public static void main(String[] args) {
//声明一个原子变量
AtomicInteger atomicInteger = new AtomicInteger(2020);
//如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
atomicInteger.compareAndSet(2020,1999);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(1999,2020);
System.out.println(atomicInteger.get());
}
}
compareAndSet:比较并交换,看一下源码:
public final boolean compareAndSet(int expect, int update) {
//变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值,并返回true。如果当前状态值不等于预期值,则返回false。
由于java不能操作内存,需要通过本地(native)方法来访问,UnSafe类在sun.misc包中,其内部方法操作可以像C语言的指针一样直接操作内存。UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用底层资源执行响应的任务。
4.UnSafe类
比较并交换
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//var5是内存地址中的值
var5 = this.getIntVolatile(var1, var2);
//compareAndSwapInt底层比较并交换
//var5 + var4指内存地址+1
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
5.CAS缺点
- 循环时间太长,开销大
AtomicInteger的getAndIncrement()方法的底层源码的实现是一个自旋锁。方法执行时有个都 do while,如果CAS失败,会一直尝试,会给CPU带来很大的开销。
-
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作。但是,对多个共享变量操作时,CAS就失去了原子性。
-
ABA问题
什么是ABA问题?
比如说一个线程1从内存位置v中取出A,这个时候另一个线程2也从内存中取出A,
然后线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成了A,
这个时候线程1进行了CAS操作发现内存中依然是A,然后线程1操作成功。
如果只关注结果,不关注过程,没有什么问题。但是过程不代表是没有问题的。
示例代码:
package com.ning.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 16790
* ABA问题
*/
public class AbaDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
new Thread(()->{
System.out.println(atomicInteger.compareAndSet(2020, 1999));
System.out.println(atomicInteger.compareAndSet(1999, 2020));
},"t1").start();
new Thread(()->{
//暂停2秒,保证前面的操作完成
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(2020, 2345));
System.out.println(atomicInteger.get());
},"t2").start();
}
}
ABA问题解决
原子引用:JDK的atomic包中AtomicReference
AtomicReference是作用是对"对象"进行原子操作的。对应的思想:乐观锁。
带版本号的原子操作。
示例:
package com.ning.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author 16790
* 原子操作
*/
public class AtomicDemo {
public static void main(String[] args) {
//AtomicStampedReference 如果泛型是一个包装类,注意对象的引用问题
//正常在业务操作,这里面比较的都是一个个对象
AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(1,1);
new Thread(()->{
//获取版本号
int a1 = atomicStampedReference.getStamp();
System.out.println("a1=>"+a1);
//比较并交换
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(2,1,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
},"a").start();
//乐观锁的实现
new Thread(()->{
int b1=atomicStampedReference.getStamp();
System.out.println("b1=>"+b1);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,3,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}
总结:
-
CAS全称为Compare And Swap ,它是一条CPU并发原语。
-
CAS的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。
-
CAS并发原语在java语言中的体现就是sun.miscUnSafe类中的各个方法。