【19】CAS基本原理

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

【19】CAS基本原理

1.什么是原子操作?如何实现原子操作?

(1)Compare And Swap:比较并交换

(2)JDK里面实现了很多原子操作类

在这里插入图片描述

(3)原子操作类就是不可再分的类。

(4)原子操作类的意思就是这个操作要么全部完成,要么全部都不完成。

(5)例如synchronized所修饰的方法或代码块,就是一个典型的原子操作,即要么操作完,要么就等着什么也不做,一直等到拿到锁。

(6)用synchronized完全可以实现原子操作。

(7)synchronized是一个很重的原子操作。谁抢到这把锁,哪个线程就进入执行,没有抢到锁的,照样安心等待。假如说操作非常简单,比如说只有一个i++,需要引入一个很重的操作代价就很大。

(8)对于比较简单的操作,是否有更轻量级的实现同步机制,不致于到锁里面去等待呢?
为了解决这个问题,在现在的CPU里面提供了一个比较并且交换的指令,即CAS指令.Compare And Swap.操作要么全部完成,要么全部没有完成。

2.CAS的原理

JDK里面如何利用CAS指令来实现Atomic开头的一些类呢?

(1)利用了现代处理器都支持的CAS的指令。

(2)循环这个指令,直到成功为止。

在这里插入图片描述

(3)现代CPU里面提供了一个专门的CAS指令,CAS指令的作用是将某一个变量的值,首先进行一个比较,比较什么?比较是否我所希望的旧值,如果是我所希望的旧值,说明没有其他线程改过,就把这个旧值变成一个新值。如果说这个值不是我所希望的旧值,就再把整个CAS操作再来执行一次。

3.悲观锁与乐观锁

3.1悲观锁

使用synchronized关键字上的锁是悲观锁,为什么是悲观?

(1)它在执行操作的时候,先将锁抢到了,安安心心做操作,做完了,就不需要管其他线程的操作了。
(2)它认为总有人要改东西,还不如先下手为强。

3.2乐观锁

(1)它会觉得进入一个操作的时候,没有人会改东西,不管怎么样,先把值算出来再讲。

(2)虽然它很乐观,不代表它是很笨,万一在计算的过程当中,有人改了值,这个时候就用CAS指令进行比较与交换。没人改,就执行自己的操作。

(3)被人改了,就再来一遍。

悲观锁的思想就是肯定有人会改我的,那我就先下手为强,先锁上。乐观锁的思想就是可能有人要修改,我就通过CAS指令比较与交换,改了就重来计算一遍,完成自己的操作。

3.3悲观锁与乐观锁哪个性能更强呢?

在这里插入图片描述

(1)原子变量(如AtomicInteger)要比加锁情况(如ReentrantLock)大部分的情况效率要高出很多。

(2)道理是,多个线程去竞争的时候,任一时刻只能有一个线程能够进入锁的范围之内,其它没有竞争成功的就被阻塞了。一旦被阻塞了,就会发生上下文切换,一次上下文切换所牵涉到的资源是非常多的,一次上下文切换所花费的时间周期大概在3~5ms之间,而CPU执行一个CAS指令的时间大概为0.6ns,所以两相比较差异是巨大的,CPU执行一条CAS指令比一次上下文切换所消耗的时间要少很多,而且上下文的切换是非常的频繁的。

(3)当使用synchronized去拿锁的时候,会发生一次上下文切换,执行完成后,再次拿锁又会发生上下文切换,也就是说去拿锁的过程至少会发生两次上下文切换,即上下文切换的时间3-5ms还需要*2,花的时间就更多了。

(4)而执行CAS指令循环机制中,线程不会主动进入被阻塞状态,它会不断的去重试,所以乐观锁的效率大于悲观锁的效率。

(5)所以JDK并发编程整个发展趋势就是采用CAS机制,向无锁化编程机制迁移。包括synchronized关键字底层实现里面也大量使用类似于自旋相关的概念。

4.CAS的问题

为什么加锁机制还有生存的余地?因为CAS也有其弱点。

4.1ABA问题

(1)CAS操作里面,中间环节修改了某个值后又快速的还原成原来的值。
生活举例:一杯水被人喝过一口之后,再到水龙头上再接点水,让人感觉没被人喝过一样。

(2)解决ABA问题需要添加一个版本戳。
即在杯子上贴个记数器,只要有人拿起杯子,我的记数器就变一次,拿一次变一次,在离开之前,记数器是0,记数器没有变,说明杯子没有人动过,只要记数器被改了,就说明杯子有人动过了。

(3)即要求线程在改值之前,要带上一个版本戳,JDK里面也提供了使用版本戳解决ABA问题的实现。例如AtomicMarkableReference,AtomicStampedReference.

  • AtomicMarkableReference只关注CAS操作中有没有线程对变量进行修改。
  • AtomicStampedReference不但关注有没有线程对变量进行修改,还关心被动过几次。
4.1.1带版本戳的原子操作
/**
 *类说明:演示带版本戳的原子操作类
 */
public class UseAtomicStampedReference {
    static AtomicStampedReference<String> asr
            = new AtomicStampedReference("mark",0);

    public static void main(String[] args) throws InterruptedException {
        //拿到当前的版本号(旧)
        final int oldStamp = asr.getStamp();
        final String oldReference = asr.getReference();
        System.out.println(oldReference+"============"+oldStamp);

        Thread rightStampThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":当前变量值:"
                        +oldReference + "-当前版本戳:" + oldStamp + "-"
                  + asr.compareAndSet(oldReference,
                        oldReference + "+Java", oldStamp,
                        oldStamp + 1));
            }
        });

        Thread errorStampThread = new Thread(new Runnable() {
            @Override
            public void run() {
                String reference = asr.getReference();
                System.out.println(Thread.currentThread().getName()
                        +":当前变量值:"
                        +reference + "-当前版本戳:" + asr.getStamp() + "-"
                        + asr.compareAndSet(reference,
                        reference + "+C", oldStamp,
                        oldStamp + 1));
            }
        });
        rightStampThread.start();
        rightStampThread.join();
        errorStampThread.start();
        errorStampThread.join();

        System.out.println(asr.getReference()+"============"+asr.getStamp());
    }
}

4.2开销问题

(1)在CAS操作里面,线程是不会休息的,如果在多个线程产生激烈竞争的情况下面,当前线程就会不断的循环比较与交换,会造成CPU非常忙.如果线程的自旋或者说循环的次数长期不成功,对CPU的开销是非常大的。

4.3只能保证一个共享变量的原子操作

(1)要比较内存中的某个变量的值。
(2)CPU指令在进行CAS操作的时候,只能针对某个地址上的值进行修改,在计算机里面一个地址只能保存一个变量,因此它只能保证一个共享变量的原子操作。如果代码块里面,假设同时要对多个变量进行修改,比如说同时改了A,又同时改了B,C,我希望对这三个变量的修改是一个原子操作,此时的CAS就不太适合了,反而是加锁的机制更适合,synchronized,由加锁来保证A、B、C变量同时修改,让其成为一个原子操作。

5.JDK中相关原子操作类的使用

在这里插入图片描述

5.1基本类型的原子操作类

/**
 *类说明:演示基本类型的原子操作类
 */
public class UseAtomicInt {
    static AtomicInteger ai = new AtomicInteger(10);

    public static void main(String[] args) {
        ai.getAndIncrement();

        ai.incrementAndGet();

        //ai.compareAndSet();
        ai.addAndGet(24);
        ai.getAndAdd(24);
    }
}

5.2引用类型的原子操作类

/**
 *类说明:演示引用类型的原子操作类
 */
public class UseAtomicReference {
    static AtomicReference<UserInfo> atomicUserRef;
    public static void main(String[] args) {
        UserInfo user = new UserInfo("Mark", 15);//要修改的实体的实例
        atomicUserRef = new AtomicReference(user);
        UserInfo updateUser = new UserInfo("Bill",17);
        atomicUserRef.compareAndSet(user,updateUser);

        System.out.println(atomicUserRef.get());
        System.out.println(user);
    }
    
    //定义一个实体类
    static class UserInfo {
        private volatile String name;
        private int age;
        public UserInfo(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }

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

}

6.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

6.1微信打赏

在这里插入图片描述

6.2支付宝打赏

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值