JUC并发编程--深入了解CAS

1.什么是CAS?
CAS:Conmpare And Swap =比较和交换
    在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子
指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置
的内容修改为新的给定值。

java中,在JDK5之前java语言是靠synchronized关键字保证同步,导致存在以下几个问题:

  1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
  2. 一个线程持有锁会导致其他所有需要此锁的线程挂起。
  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级导致,引起性能风险。
synchronized是一种悲观锁,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁就是每次不加锁而是假设没有冲突而且完成某项操作,如果因为冲突失败就重试,
直到成功为止。乐观锁用到的机制就是CAS。
2.CAS主要代码是放在JUC的atomic包下

图片

3.CAS的使用
package com.ning.cas;

import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author 16790
 * CAS操作
 */
public class CASDemo {

    public static void main(String[] args) {
        //声明一个原子变量
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
        atomicInteger.compareAndSet(2020,1999);
        System.out.println(atomicInteger.get());

        atomicInteger.compareAndSet(1999,2020);
        System.out.println(atomicInteger.get());
    }
}

compareAndSet:比较并交换,看一下源码:

 public final boolean compareAndSet(int expect, int update) {
   //变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值,并返回true。如果当前状态值不等于预期值,则返回false。

由于java不能操作内存,需要通过本地(native)方法来访问,UnSafe类在sun.misc包中,其内部方法操作可以像C语言的指针一样直接操作内存。UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用底层资源执行响应的任务。

4.UnSafe类

在这里插入图片描述
比较并交换

/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
      int var5;
      do {
      //var5是内存地址中的值
          var5 = this.getIntVolatile(var1, var2);
          //compareAndSwapInt底层比较并交换
          //var5 + var4指内存地址+1
      } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
      return var5;
  }

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

5.CAS缺点
  1. 循环时间太长,开销大
    在这里插入图片描述

AtomicInteger的getAndIncrement()方法的底层源码的实现是一个自旋锁。方法执行时有个都 do while,如果CAS失败,会一直尝试,会给CPU带来很大的开销。

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

    当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作。但是,对多个共享变量操作时,CAS就失去了原子性。

  2. ABA问题

  什么是ABA问题?

  比如说一个线程1从内存位置v中取出A,这个时候另一个线程2也从内存中取出A,
  然后线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成了A,
  这个时候线程1进行了CAS操作发现内存中依然是A,然后线程1操作成功。
  如果只关注结果,不关注过程,没有什么问题。但是过程不代表是没有问题的。

示例代码:

package com.ning.cas;

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

/**
 * @author 16790
 * ABA问题
 */
public class AbaDemo {
    public static void main(String[] args) {

        AtomicInteger atomicInteger=new AtomicInteger(2020);
        
        new Thread(()->{
            System.out.println(atomicInteger.compareAndSet(2020, 1999));
            System.out.println(atomicInteger.compareAndSet(1999, 2020));
        },"t1").start();

        new Thread(()->{
            //暂停2秒,保证前面的操作完成
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(2020, 2345));
            System.out.println(atomicInteger.get());

        },"t2").start();
    }
}

ABA问题解决

原子引用:JDK的atomic包中AtomicReference
在这里插入图片描述

AtomicReference是作用是对"对象"进行原子操作的。对应的思想:乐观锁。

带版本号的原子操作。

示例:

package com.ning.cas;

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

/**
 * @author 16790
 * 原子操作
 */
public class AtomicDemo {
    public static void main(String[] args) {

        //AtomicStampedReference 如果泛型是一个包装类,注意对象的引用问题
        //正常在业务操作,这里面比较的都是一个个对象
        AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(1,1);

        new Thread(()->{
            //获取版本号
            int a1 = atomicStampedReference.getStamp();
            System.out.println("a1=>"+a1);

            //比较并交换
            atomicStampedReference.compareAndSet(1,2,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

            System.out.println("a2=>"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(2,1,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

            System.out.println("a2=>"+atomicStampedReference.getStamp());
        },"a").start();
        
        //乐观锁的实现
        new Thread(()->{
            int b1=atomicStampedReference.getStamp();
            System.out.println("b1=>"+b1);

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

            atomicStampedReference.compareAndSet(1,3,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();
    }
}

总结:

  1. CAS全称为Compare And Swap ,它是一条CPU并发原语。

  2. CAS的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。

  3. CAS并发原语在java语言中的体现就是sun.miscUnSafe类中的各个方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值