CAS是什么?
CAS(Compare And Swap)是一条cup并发原语,其本质是比较并交换。
CAS实例
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
atomicInteger.compareAndSet(5, 2019);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2019, 2000);
System.out.println(atomicInteger.get());
}
}
CAS的底层
自旋锁 + Unsafe类
什么是Unsafe类
由于java无法直接访问的底层系统内存,不像c语言那样可以直接通过指针,访问变量的存储地址。java需要通过本地方法访问底层系统数据存储地址,而Unsafe类弥补了 这一局限性,相当于一个后门。Unsafe提供了很多的native方法可以直接访问操作特定内存的数据。与c语言的指针类似。
什么是自旋锁
自旋锁是一种为了保护共享资源而提出的一种锁机制。
代码原理如下:
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;
}
在上面的代码中
var1 就是对象进行操作的对象引用
var2 是该对象引用在内存的偏移量,也就是起始地址
var5 是期望值
var5 + var4 是待更新的值
这段代码的含义就是根据共享变量的存储地址获取该变量的值,判断该变量的值是否等于期望值,如果相等则将待更新主内存中该变量的值为最新的值。反之,继续进行比较,直到主内存中的值和期望值一致为止。
CAS并发原语体现在Java语言就是Unsafe类的各个方法,java条用Unsafe类的方法,JVM会自动帮我们实现CAS的汇编指令,这是一种完全依赖于硬件的功能,通过它体现了原子性操作,CAS是系统原语,属于操作系统指令,由若干条指令组成的代码块,执行必须是连续性,中间不能被中断。
CAS的缺点:
1、循环时间长,内存消耗大;
如果线程数量比较多的话,CAS请求失败会一直循环下去,这样的话CPU的开销会比较大。
2、只能对一个共享变量进行原子性的操作
3、会出现ABA问题。
什么是ABA问题?
由于CAS无法对主内存共享变量的操作记录,比如,更改次数,是否更改。这容易让一个线程将给A修改成B,而另一个线程又将B修改成A造成CAS多次执行的问题。也就是开头和结尾是一样的,中间过程发生了变化。
原子引用
原子引用AtomicReference是保证基本类型的原子性,对于一个类而言,可以使用原子引用进行封装。
AtomicReference<User> userAtomic = new AtomicReference<>();
如何解决ABA问题
ABA问题的本质就是通过CAS多次改变了主内存的共享变量的值,而最终又改为最初的值。头尾一样,中间的操作对于其他线程来说是不可知的,这可能会造成CAS的多次执行。
而想要解决ABA问题的办法就是让其他线程能够知道这个变量中间有被更改过,我们只在变量每一次被修改时添加一个记录就可以解决了这个一个问题。
AtomicStampedReference 在原有的AtomicReference上添加了版本号的概念。
使用案例
public class ABADemo { //ABA解决问题,AtomicStampedReference
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 100) + "\t" + atomicReference.get());
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("*******************演示ABA的解决********************");
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); //获取版本号
System.out.println(Thread.currentThread().getName() + "当前版本号:" + stamp);
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace();}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "当前版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "当前版本号:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "当前版本号:" + atomicStampedReference.getStamp());
try{ TimeUnit.SECONDS.sleep(3);}catch (InterruptedException e){e.printStackTrace();}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "修改成功?" + result + "当前最新版本号" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "当前线程实际最新值" + "\t" + atomicStampedReference.getReference());
}, "t4").start();
}
}
使用时间戳原子引用,当某个线程在修改值时,发现虽然内存中的值和预期值一样,但是由于版本号已经发生了该改变,所以修改失败,这时候需要重新获取该变量的版本号和变量的值再进行修改,并标记新的为新的版本。