ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JUC高级多线程_12:CAS与ABA问题

2021-07-05 11:59:46  阅读:179  来源: 互联网

标签:JUC 12 CAS System atomicInteger atomic println 多线程 out


我是 ABin-阿斌:写一生代码,创一世佳话。 如果小伙伴们觉得我的文章有点 feel ,那就点个赞再走哦。
在这里插入图片描述

文章目录

一、CAS简介

  • 全称 Compare and swap,字面意思:【比较并交换

1. CAS有哪些操作:

  • 需求场景: 我们假设内存中的原数据 V,旧的预期值 A,需要修改的新值 B
  • 三步操作:
    1. 比较 AV 是否相等。(比较)
    2. 如果比较相等,将 B 写入 V。(交换)
    3. 返回操作是否成功。

2. 注意事项:

  • 当多个线程同时对某个资源进行 CAS 操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。

3. 代码演示:

/**
 * @description: Compare and swap 简称:CAS 中文:比较并交换
 */
public class CASDemo {
    public static void main(String[] args) {
        //原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理
        AtomicInteger atomicInteger = new AtomicInteger(2021);
        //比较并设置,参数一:期望值   参数二:更新值
        boolean update = atomicInteger.compareAndSet(2021, 2022);
        if (update) {
            System.out.println("修改成功:" + atomicInteger.get());
        } else {
            System.out.println("修改失败!:" + atomicInteger.get());
        }

        //以原子方式将当前值递增+1
        atomicInteger.getAndIncrement();
        boolean flag = atomicInteger.compareAndSet(2021, 2022);
        if (flag) {
            System.out.println("修改成功:" + atomicInteger.get());
        } else {
            System.out.println("修改失败!:" + atomicInteger.get());
        }
    }
}

运行结果:
在这里插入图片描述

AtomicInteger 类介绍:

  • java.util.concurrent.atomic 的包下的原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理。
  • 不单单这一个类,还有 AtomicBoolean,AtomicInteger,AtomicLong,AtomicLongArray,AtomicReference 等也都是同上。

AtomicInteger 源码解析:

在这里插入图片描述

getAndIncrement() 方法源码分析:

  • 从下方源码中我们可看到Java是直接从内存中获取的,所以效率是非常高的
  • 同时,下方白色源码当中是 一种锁机制: 自旋锁, 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作。如果不是,那么(do while 无限循环)

在这里插入图片描述

4. CAS的缺点

  • 性能问题,我们使用时大部分时间使用的是 while true 方式对数据的修改,直到成功为止。
  • 优势就是相应极快,但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。
  • ABA问题

二、ABA问题

1. 什么是ABA

  • 比如 张三现在查询学生表中的语文成绩是96,当张三想要修改这个数据的时候又去查了一遍 还是 96。那么在这个时候我们从直观的角度来看这个数据是没有被别人修改过的,但是它真的没有被修改过吗?
  • 其实不然,在张三查询第二遍的这段时间中,这个 96 很有可能被李四改成 69,结果李四一看不对呀,改错了,又慌慌张张的改成 96 了
  • 那么,CAS 就会误认为这个值一直都是 96 从未改过。这是不是就有问题了,所有这个问题我们就统称为:CAS操作的——>ABA问题。

2. 代码演示

/**
 * @description: CAS:ABA问题测试
 */
public class ABADemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(96);
        // 如果我期望的值达到了,那么就更新,否则,就不更新
        System.out.println(atomicInteger.compareAndSet(96,100));
        System.out.println(atomicInteger.get());

        // ============== 捣乱的李四 ==================
        System.out.println(atomicInteger.compareAndSet(100,69));
        System.out.println(atomicInteger.get());

        // ============== 期望的结果 ==================
        System.out.println(atomicInteger.compareAndSet(69,100));
        System.out.println(atomicInteger.get());
    }
}

结果:

在这里插入图片描述

3. 如何避免ABA问题

  • 原子引用:(乐观锁思想) AtomicStampedReference 类可以解决ABA问题。这个类维护了一个【版本号】Stamp,其实有点类似乐观锁的意思。在进行 CAS 操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。

注意:

  • AtomicStampedReference<>(6, 1); 这两个参数不要填的太大,因为我们本次测试用的是 Integer 类型
  • Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new一定会创建新的对象分配新的内存空间

代码演示:

/**
 * @description: 解决 CAS 带来的 ABA问题
 */
public class ABADemo2 {

    static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(6, 1);

    public static void main(String[] args) {

        //使用版本号机制来验证ABA问题
        new Thread(() -> {
            //获取当前版本号
            int stamp = atomic.getStamp();
            System.out.println("线程A1的版本号为:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomic.compareAndSet(6, 10, atomic.getStamp(), atomic.getStamp() + 1);
            System.out.println("线程A2的版本号为:" + atomic.getStamp());

            System.out.println(atomic.compareAndSet(10, 6, atomic.getStamp(), atomic.getStamp() + 1));
            System.out.println("线程A3的版本号为:" + atomic.getStamp());

        }, "线程A:").start();

        //使用【乐观锁】思想解决ABA问题
        new Thread(() -> {
            int stamp = atomic.getStamp();
            System.out.println("线程B1的版本号为:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomic.compareAndSet(6, 2, stamp, stamp + 1));
            System.out.println("线程B2的版本号为:" + atomic.getStamp());

        }, "线程B:").start();
    }
}

结果展示:

在这里插入图片描述

标签:JUC,12,CAS,System,atomicInteger,atomic,println,多线程,out
来源: https://blog.csdn.net/Mango_Bin/article/details/118418321

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有