CAS是什么?ABA问题的产生和解决方法

CAS是什么?

比较并交换(compare and swap)是一条CPU并发原语java

功能

判断内存中某个位置的值是否为预期值,若是是则更改成新的值,这个过程是原子的,中间不予许中断,解决数据一致性问题。mysql

底层原理

Unsafe类

是CAS的核心类,因为java没法直接访问底层系统,须要经过本地(native)方法访问,Unsafe至关于一个后门,该类能够直接操做特定的内存数据。 Unsafe类存在于sun.misc包中,其内部方法操做能够像C的指针同样直接操做内存,由于java中的CAS依赖于Unsafe类中的方法 算法

注意 Unsafe中的全部方法都是native修饰的,就是说Unsafe中的方法都是直接操做系统底层资源执行任务sql

底层汇编

底层代码

 // AtomicInteger类中方法:getAndIncrement,调用Unsafe类中的getAndAddInt
 public final int getAndIncrement(){
     return unsafe.getAndAddInt(this,valueOffset,1);
}
//Unsafe类中
 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是AtomicInteger对象自己并发

  • var2是对象值的引用地址this

  • var4是须要变更的数值atom

  • var5是经过var一、var2找出的内存中的真实的值 方法:用对象的值和var5做比较,若是相同,则更新var5+var4而且返回TRUE,若是不一样则继续取值而后再比较,直到更新完成spa

 

 

 

 

缺点

一、 循环时间开销大操作系统

二、只能保证一个共享变量的原子操做

三、 引出ABA问题

 

ABA问题

CAS算法实现一个重要前提须要提取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会致使数据的变化。

举例:

一个线程one从内存位置V中取出A,这时候另外一个线程two也从内存中取出A,而且线程two进行了一些操做将A变成了B,而后又将V位置的数据变成了A,而这时候线程one进行 CAS操做的时候发现内存中仍然是A,而后one线程提示操做成功。

尽管one线程的CAS操做成功,可是不表明这个线程是没问题的

 

代码还原

package com.dayu.inter; import sun.rmi.runtime.NewThreadAction; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * @Author: dayu * @Date: 2019/9/24 15:13 * @Description */
public class ABADemo { public static void main(String[] args) { System.out.println("=========下面是ABA问题的产生=========="); AtomicReference<Integer> atomicReference = new AtomicReference<>(100); new Thread(() -> { System.out.println(atomicReference.compareAndSet(100, 101)+"\t"+atomicReference.get()); System.out.println(atomicReference.compareAndSet(101, 100)+"\t"+atomicReference.get()); }, "t1").start(); new Thread(() -> { //休息一会,让线程t1先执行一遍ABA的问题
            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(atomicReference.compareAndSet(100, 2000)+"\t"+atomicReference.get()); }, "t2").start(); //休息一会,确保上面两个线程执行完毕
        try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();} System.out.println("=========下面是ABA问题的解决=========="); //有点相似于乐观锁 //初始值设定100,时间戳(版本号=1)
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第一次获取版本号"+stamp); //休息一会,等待t4获取版本号
            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()+"\t 第2次获取版本号"+atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第3次获取版本号"+atomicStampedReference.getStamp()); }, "t3").start(); //t4和t3最初获取到的版本号一致,
        new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第一次获取版本号"+stamp); //休息一会,确保t3完成一次ABA
            try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();} boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t 是否修改为功:"+result+"\t 当前真实的版本号:"+atomicStampedReference.getStamp() +"\t 当前真实的值:"+atomicStampedReference.getReference()); }, "t4").start(); } }

 

为了解决ABA问题,在原子引用类上加上版本号,这个有点相似于mysql的乐观锁同样,每一个线程更改一次都须要更改版本号,那么多线程同时获取到同一个版本号的时候也只有一个线程能够更改为功。

相关文章
相关标签/搜索