CAS了解一下

今天咱们还是给自个充充电,就不大战秃头老了!等冲个差不多,电死!

今天我们就来聊一下CAS,这个玩意,太重要了,是并发包(JUC)的基础,没有它可以说是并发包简直就是废废的,说它之前咱们先讨论一下线程不安全的场景,并尝试解决一下!


目的:怎么让一个变量的快速到达200?

/**
 * @author: tianjx
 * @date: 2022/1/2 16:22
 * @description: 线程不安全问题!
 */
public class CASDemo04 {

    // 类的成员变量
    static int data = 0;

    // main方法内代码
    public static void main(String[] args) {
       IntStream.range(0,5).forEach((i)->{
           new Thread(()->{
               IntStream.range(0,40).forEach((j)->{
                   data++;
               });
           }).start();
       });
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(data);
    }
}

在这里插入图片描述

哦?发现用多线程,竟然会造成结果不等于200!(假设A,B线程同时拿到data(100),A快一点,A现在工作内存中改成101,然后写到主内存,然后B开始,先在工作内存改成101,然后在写到主内存101,明明加了两次,但是结果确实101!)

啊!这可怎么办?那肯定就是加锁吧!

方法一:那…synchronized登场吧,我们常用的!

/**
 * @author: tianjx
 * @date: 2022/1/2 16:39
 * @description: synchronized 解决线程不安全
 */
public class CASDemo05 {
    // 类的成员变量
    static int data = 0;

    // main方法内代码
    public static void main(String[] args) {
        IntStream.range(0,5).forEach((i)->{
            new Thread(()->{
                synchronized (CASDemo05.class){
                    IntStream.range(0,40).forEach((j)->{
                        data++;
                    });
                }
            }).start();
        });
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(data);
    }
}

这个确实可以,但是我们都清楚synchronized是悲观锁,它会造成以下缺陷:

1、如果根据时间片来获取锁,加锁,释放锁,再加锁,在释放锁,那么就会造成频繁的上下文切换,线程一多,反而多线程的性能可能还不如单线程!

2、一个线程持有锁之后,所有的线程都会被阻塞或者挂起!这不是假多线程嘛!

(其实synchronized也做过很多的优化,刚刚开始可不是重量级锁哦!如果大家也想了解了解,请双击屏幕点个赞,并且关注不迷路哦!跪谢!)

方法二:lock(朋友莫慌,后面会介绍)

public class CASDemo06 {
    // 类的成员变量
    static int data = 0;
    static Lock lock = new ReentrantLock();

    // main方法内代码
    public static void main(String[] args) {
        IntStream.range(0,5).forEach((i)->{
            new Thread(()->{
                IntStream.range(0,40).forEach((j)->{
                    lock.lock();
                    data++;
                    lock.unlock();
                });
            }).start();
        });
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(data);
    }
}

方法三:AtomicInteger(朋友莫慌,后面会介绍)

/**
 * @author: tianjx
 * @date: 2022/1/2 16:54
 * @description: Atomic
 */
public class CASDemo07 {
    // 类的成员变量
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    // main方法内代码
    public static void main(String[] args) {
        IntStream.range(0,5).forEach((i)->{
            new Thread(()->{
                IntStream.range(0,40).forEach((j)->{
                    atomicInteger.incrementAndGet();
                });
            }).start();
        });
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger.get());
    }
}

方法四:LongAdder(朋友莫慌,后面会介绍)

/**
 * @author: tianjx
 * @date: 2022/1/2 16:58
 * @description:
 */
public class CASDemo08 {
    // 类的成员变量
    static LongAdder longAdder = new LongAdder();

    // main方法内代码
    public static void main(String[] args) {
        IntStream.range(0,5).forEach((i)->{
            new Thread(()->{
                IntStream.range(0,40).forEach((j)->{
                    longAdder.increment();
                });
            }).start();
        });
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(longAdder);
    }
}
  • Lock底层是用AQS+CAS来实现的,在高并发的场景下,比synchronized性能高的可不是一点半点
  • AtomicIntegerJUC并发包下的,底层实现也是CAS,相对于lock不断加锁,释放锁更加优雅!
  • LongAdder是JDK1.8之后新增的类,也是JUC并发包下的,不用说实现也是用了CAS,不过哦,他很有特点,它比上面更适合在高并发场景下,写的次数大于读的次数!

多线程要保证线程安全可是非常重要的,而且上面的方法多次提到了CAS,我们必须的聊聊了!(可能前奏有点多,但是上面知识可能会在工作中常用,所以就放到前面了!)


先简单介绍下它,CAS其实就是compare and swap的缩写,比较并交换,他是一种乐观锁,也是一种无锁,认为大概率是不需要加锁的,如果需要加锁就进行比较并交换!

接下来我们说下原理,他又三个参数v,o,nv表示内存中实际存放的值,o表示预期的值,n表示要修改的值,如果v==o,那么我们则把值改成n,如果不相等则更改失败,返回o!(就是这么简单,别惊讶!JUC包下的类,大部分都调用的是unsafe的cas方法,可能参数或多或少,基本上都是这个的变种,八九不离十!)


我们已经了解了它并且还知道了它的原理,但是大家有没有一种疑惑!假如又两个线程AB,A线程发现v==o,准备更改为n,但是B线程来了,也发现v==o准备改成另外一个值,这不并没有实现线程安全嘛!这不吹牛那?

是这样的,自从JNI的出现,我们java也可以越过JVM,调用操作系统原语了,而CAS就是一种系统原语,而系统原语就是属于操作系统原语了,操作系统原语可能是若干条指令,但是一个原语是不能被其他终端打断的,所以并不会出现上面的问题!

在这里插入图片描述


最后的最后我们再聊聊CAS有什么优缺点把!

优点:采用无锁的方式,在性能上可能会有不小的提升

缺点:1、会造成ABA问题,2、只能保证一个共享变量(不过可以进行变量合并成对象来玩)


最后的最后的最后在聊聊ABA问题!

臭名昭著的ABA问题,即第一个线程把A把值改成B,然后在改成A,第二个线程发现没有符合期望值,那执行呗!

其实这不完全算问题,有些工作场景并不关心这个,只要最后的值相同就行!比如买了一张火车票,然后秒退,这时候火车票总数不变,不影响!但是有些场景很注重次数,可能这个就很严重了,假如高考试卷,有个人偷偷打开了保险箱,把试题拍了下来,第二个人来的时候是看不出来的,但是这样就造成了泄题!

这时候我们其实加个版本号就行了,每次操作算一个版本,比较值的同时,在比较版本号就行了,具体实现的类有AtomicStampedReference等等!

在这里插入图片描述

参考博客:一文彻底搞懂CAS实现原理_东升的思考-CSDN博客

参考博客:CAS是什么?彻底搞懂CAS_阿杰-CSDN博客_cas

以上只是我的简单理解,如果不足之处,请大家指出!

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值