5.并发编程---CAS

CAS原理

利用了现代的处理器都支持的CAS的指令,然后循环这个指令,直到成功为止。
CAS(Compare And Swap)指令级别保证了原子操作。
三个运算符号:V内存地址,A期望值,B新的值
原理实现:通过自旋方式(死循环)不断的进行CAS操作。如果地址V上的值等于期望值A,内存地址V就把值赋值给新值B。如果不等于的话,不做任何操作

CAS问题

ABA问题与解决

问题:比如内存地址有一个值V,这时候线程A对V进行-1,然后线程B对V进行+1。虽然线V值和以前一样,但是实际上它已经变化了两次,V->A->B->V。举个列子:你桌子上有一杯水,然后你出去一趟,桌子上的水被张三偷喝了一口,然后张三又给你倒满了,你回来一看,杯子水还在就直接喝下去了。。。实际上你的水杯水已经发生了变化。
解决:可以加一个版本号进行控制,当V值一旦发生改变就可以立即见到。v–>A–v1->B–>v2

开销问题

为什么说消耗资源呢。根据他的实现原理可以知道,它采用的是自旋方式在不断的进行cas操作,一旦cas长期不成功,他就会一直在自旋。

只能保证一个共享变量操作

JDK中CAS类

在这里插入图片描述

Atomic

作用:Atomic家族主要是保证多线程环境下的原子性,相比synchronized而言更加轻量级。
注意点:期望值一旦被修改后,就无法再次更改,如果想要修改,必须保证新的期望值必须是上一次的目标新值。

AtomicReference<引用类型>

作用:保证一个引用类型在多线程的情况下是线程安全的。将新的值修改成期望值。
查看结果后返回AtomicReference< UserInfo >里面的对象相当于一个新的引用,结果改变成yt对象的值,但是原有的ms和yt对象并未改变。这就是原子性。
多线程下修改引用对象值
1:期望值一旦被修改后,就无法再次更改

 public static void main(String[] args) {
        //初始化一个新对象后,将新对象加入到原子操作中
        UserInfo ms = new UserInfo("ms", 25);
        atomicReference.set(ms);
        for (int i = 1; i < 10; i++) {
            int finalI = i;
            new Thread(()->{
                UserInfo userInfo = new UserInfo("yt" + finalI, 26 + finalI);
                boolean b = atomicReference.compareAndSet(ms, userInfo);
                System.out.println("第【"+ finalI +"】次修改的结果;"+b);
            }).start();
        }
        SleepTools.sencod(3);
        System.out.println("打印多线程情况下修改后的值");
        System.out.println(atomicReference.get().getName()+"---"+atomicReference.get().getAge());
    }
第【1】次修改的结果;true
第【2】次修改的结果;false
第【3】次修改的结果;false
第【4】次修改的结果;false
第【5】次修改的结果;false
第【6】次修改的结果;false
第【7】次修改的结果;false
第【9】次修改的结果;false
第【8】次修改的结果;false
打印多线程情况下修改后的值
yt1---27

观看输出结果可以发现,只有第一个线程才可以把目标值改成期望值,其他线程已经无法修改目标值。

AtomicStampedReference

作用:解决ABA问题,主要作用就是对修改值加一个版本号控制。

/*
 * 解决ABA问题,主要作用就是对修改值加一个版本号控制
 * */
public class AtomincStampedDemo {
    //初始化一个期望值,期望版本号为0
    static AtomicStampedReference<String> stamp = new AtomicStampedReference<String>("1L的水",0);
    public static void main(String[] args) throws InterruptedException {
        //获取最初的值和版本号
        String oldReference = stamp.getReference();
        int oldStamp = AtomincStampedDemo.stamp.getStamp();
        System.out.println("期望值="+oldReference+"-------期望版本号="+oldStamp);
        //两个线程对版本号进行修改
        Thread thread1 = new Thread(() -> {
            //期望值,新值,期望版本,新版本
            boolean b =stamp.compareAndSet(oldReference, "2L的水", oldStamp, 1);
            System.out.println(Thread.currentThread().getName() + "修改的的新值=" + stamp.getReference()
                    + "---修改的新版本号=" + stamp.getStamp()+"修改的结果="+b);
        });
        Thread thread2 = new Thread(() -> {
            //期望值,新值,期望版本,新版本
            boolean b = stamp.compareAndSet(oldReference, "1L的水", oldStamp, 2);
            System.out.println(Thread.currentThread().getName() + "修改的的新值=" + stamp.getReference()
                    + "---修改的新版本号=" + stamp.getStamp()+"修改的结果="+b);
        });

        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        System.out.println(Thread.currentThread().getName() + "最终的值=" + stamp.getReference()
                + "---最终的版本号=" + stamp.getStamp());

    }

期望值=1L的水-------期望版本号=0
Thread-0修改的的新值=2L的水---修改的新版本号=1修改的结果=true
Thread-1修改的的新值=2L的水---修改的新版本号=1修改的结果=false
main最终的值=2L的水---最终的版本号=1

观察结果可以发现,期望值已经更改了,在用Thread2线程去更改期望值的版本号,是更改不成功的。如果想要修改成功,必须保证Thead2里的期望值是Thread1的目标新值,才可以。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值