CAS相关

{
"title":"CAS相关",
"date":2020-05-09T16:21:21+08:00,
"draft":true,
"tags":["CAS、ABA"],
"comments":true,
"share":true
}

  1. CAS来源

问题:为什么使用AtomicInteger中的getAndIncrement()能解决多线程下i++写值丢失问题?

(1)、AtomicInteger类中getAndIncrement()的源代码如下,发现调用了UnSafe类下的getAndIncrement(),如下所示。注意:UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务。

 public final int getAndIncrement() {
      return unsafe.getAndAddInt(this, valueOffset, 1);
  }

(2)、UnSafe类中的getAndAddInt(this, valueOffset, 1)源代码如下,其实底层用了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)); // CAS思想
          return var5;
      }
      说明:var1是AtomicInteger对象本身,var2 该对象值的引用地址,var4是需要变动的数值;var5 是用var1和var2找出内存中原始的值
       compareAndSwapInt(var1, var2, var5, var5 + var4)的思想:用该对象当前的值(var1和var2的值)与var5比较 ,如果相同,更新var5的值并且返回true,
       如果不同,继续取值然后比较,直到更新完成。
  1. CAS介绍

CAS的全称为Compare-And-Swap ,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。

CAS并发原语体现在Java语言中就是sun.misc.UnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令。这是一种完全依赖于硬件功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程。

package com.xpf.Interview.juc.CASDemo;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: Xia
 * @Date: 2020/4/23 10:06
 * @Email:x2358114512@163.com
 * cas(compareAndSet):比较并交换(没有加synchronized和lock等锁)
 *
public class CASDemo05 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        System.out.println(atomicInteger.compareAndSet(5,2020) +
                "\t 当前值是:"+atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(5,2021) +
                "\t 当前值是:"+atomicInteger.get());
    }
}

结果现象:

true 当前值是:2020

false 当前值是:2020

结果分析:

atomicInteger.compareAndSet(5,2020)的底层代码如下所示:

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

判断内存某个位置的值是否为预期值,如果是则更新为新的值并且返回true,如果不是则不更新并且返回false,同时这个过程是原子的。

  1. CAS的缺点

(1):如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销,

(2):CAS只能解决一个共享变量的原子操作(其底层源码中是由指当前对象this一个变量才可以,多个共享变量的保证原子性使用 synchronized)

(3):可能引起ABA问题

  1. ABA问题代码演示

ABA问题是什么?

比如一个线程t1从内存位置中取出A,这时候另一个线程t2从内存位置中取出A,并且线程t2进行了一系列操作将值变成了B,然后线程t2又将内存位置中的B变回A,这时候线程t1进行CAS操作发现内存位置中的值还是A,然后线程t1操作成功。尽管线t1的CAS操作成功,但是不代表这个过程是没有问题。

一个线程在时间差内将主物理内存中的值发生了变化但最后又变回初始的值,而另个线程并不知道它的变化过程,只知道最后的值没有变化。

如果另个线程不介意知道它的变化(不介意ABA问题,情况很少),只要另个线程写回主物理内存时其值没变化也可正常运行。如果另个线程介意知道它的变化(介意ABA问题),那么ABA问题的解决思路?

4.1 AtomicReference介绍

juc包里有原子整型、原子布尔类型等等(AtomicInteger、 AtomicBoolean),那是不是有原子订单类、原子用户类等等?juc是如何生成的原子订单类、原子用户类等等?

存在原子订单类、原子用户类等等,使用原子引用(AtomicReference)即可。如下代码所示。

class User{  //定义User类
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

package com.xpf.Interview.juc.CASDemo;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @Author: Xia
 * @Date: 2020/4/23 15:04
 * @Email:x2358114512@163.com

public class CSADemo06 {
    public static void main(String[] args) {
        User zs = new User("zs", 22);
        User ls = new User("ls", 22);

        //定义了User的原子类型
        AtomicReference<User> userAtomicReference = new AtomicReference<>();

        userAtomicReference.set(zs); //主物理内存里是zs

        System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
        System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
    }
}

结果现象:

true User{name='ls', age=22}

false User{name='ls', age=22}

4.2 ABA产生-代码演示

package com.xpf.Interview.juc.CASDemo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @Author: Xia
 * @Date: 2020/4/23 15:26
 * @Email:x2358114512@163.com
 */
public class CASDemo07 {

    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {
        csaABA_AtomicInteger_1();    //AtomicInteger演示CAS的ABA问题
        csaABA_AtomicInteger_2();    //AtomicInteger演示CAS的ABA问题
        csaABA_AtomicReference_1();  //AtomicReference演示CAS的ABA问题
        csaABA_AtomicReference_2();  //AtomicReference演示CAS的ABA问题
    }

    private static void csaABA_AtomicInteger_1() {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,127);
            atomicInteger.compareAndSet(127,100);
        },"AAA").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"线程: " +atomicInteger.compareAndSet(100,2019));
        },"BBB").start();
    }


    private static void csaABA_AtomicInteger_2() {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,128);
            atomicInteger.compareAndSet(128,100);
        },"AAA").start();

        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"线程: " +atomicInteger.compareAndSet(100,2020));
        },"BBB").start();
    }


    private static void csaABA_AtomicReference_1() {
        new Thread(() -> {
            atomicReference.compareAndSet(100,127);
            atomicReference.compareAndSet(127,100);
        },"AAA").start();

        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"线程: " +atomicReference.compareAndSet(100,2021));
        },"BBB").start();
    }


    private static void csaABA_AtomicReference_2() {
        new Thread(() -> {
            atomicReference.compareAndSet(100,128);
            atomicReference.compareAndSet(128,100);
        },"AAA").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"线程: " +atomicReference.compareAndSet(100,2022));
        },"BBB").start();
    }
}

实验结果:

运行csaABA_AtomicInteger_1()函数;    结果:【BBB线程:true】
运行csaABA_AtomicInteger_2()函数;    结果:【BBB线程:true】
运行csaABA_AtomicReference_1()函数;  结果:【BBB线程:true】
运行csaABA_AtomicReference_2()函数;  结果:【BBB线程:false】

实验结果分析:

分别运行csaABA_AtomicInteger_1()、csaABA_AtomicInteger_2()、csaABA_AtomicReference_1()三个函数,得到的结果均是:【BBB:true】。也就是说明"BBB"线程不知道"AAA"线程修改过了起始值,最后又修改为了起始值,也就是存在ABA问题。

运行csaABA_AtomicReference_2()函数后的结果是【BBB线程:false】,原因是AtomicReference

调用了public AtomicReference(V initialValue) { value = initialValue;}

JVM会自动维护八种基本类型的常量池,int常量池中初始化-128~127的范围,所以当为Integer i=127时,在自动装箱过程中是取自常量池中的数值,而当Integer i=128时,128不在常量池范围内,所以在自动装箱过程中需new一个128,所以地址不一样 。

细看:https://blog.csdn.net/BeauXie/article/details/53013946

4.3 ABA解决-代码演示

ABA问题的解决办法就是使用版本号,在变量前面追加版本号,每次变量更新时把版本号加1,那么A-B-A就会变成1A-2B-3A。

package com.xpf.Interview.juc.CASDemo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: Xia
 * @Date: 2020/4/23 15:26
 * @Email:x2358114512@163.com
 *
 *ABA问题的解决思路?
 *   CAS处理的话出现ABA问题,解决方法:1.AtomicStampedReference类,时间戳机制(类似于版本号)
 */
public class CASDemo07 {

    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        casABASolution();  //演示ABA问题的解决思路
    }

    private static void casABASolution() {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); //得到初始化的版本号
            System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp);

            //先睡眠1秒,让线程"d"也到达此位置,得到最初版本号
            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 第二次版本号:"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第三次版本号:"+atomicStampedReference.getStamp());
        },"c").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); //得到初始化的版本号
            System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp);

            //先睡眠3秒,让"c"线程出现ABA问题
            try {TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

            System.out.println(Thread.currentThread().getName()+"\t"+
                    atomicStampedReference.compareAndSet(100,2020,stamp,stamp+1));
            System.out.println("当前最新版本号:"+atomicStampedReference.getStamp());  //结果是3(版本号为3,没有成功保持之前的版本号)
            System.out.println("当前实际最新值:"+atomicStampedReference.getReference());// 结果是100,没有改为2020.
        },"d").start();

    }
}
全部评论

相关推荐

11-26 22:34
已编辑
重庆邮电大学 Java
快手 客户端开发 (n+5)k*16 公积金12
牛客895077908号:佬 什么双非硕啊
点赞 评论 收藏
分享
10-27 17:26
东北大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务