JUC多并发编程-->CAS和原子操作类

CAS是什么

类似于乐观锁
compare and swap,比较与交换,实现并发算法时常用的一种技术。
包含三个操作数— 内存位置、预期原值以及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:

  • 如果匹配,那么处理器会自动将该位置的值更新为新值。
  • 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来当它重来重试的这种行为成为----自旋! !
在这里插入图片描述

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger =new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t" +atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t" +atomicInteger.get());
        // true	2022
		// false 2022
    }
}
======调用的方法 unsafe类中
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

硬件级别保证:
在这里插入图片描述

unsafe

  1. unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native) 方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
    注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe 类中的方法都直接调用作系统底层资源执行相应任务
  2. valueOffset:表示变量值在内存中的偏移地址,因为unsafe就是根据内存偏移地址获取数据的。
  3. 变量value用valatile修饰,保证了多线程之间的内存可见性。

atomicInteger.getAndIncrement()是如何保证安全的

  • CAS十一条CPU的并发原语。他的功能是判断某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
  • AtomicInteger类主要是利用CAS+volatile和native方法来保证原子操作,避免sychornized的高开销,执行效率大为提升。
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
 =====
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若于条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

JDK提供的CAS机制,在汇编层级会禁止变量两侧的指令优化,然后使用cmpxchg指令比较并更新变量值(原子性)
Atomic::cmpxchg(x,addr,e)) ==e;
不同的操作系统会调用不同的cmpxchg重载函数
因此,CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性

自旋锁 spinlock

CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻寒,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

CAS缺点

循环时间长,开销很大
在这里插入图片描述
引出来ABA问题
在这里插入图片描述
解决方案:
可以加版本号AtomicStampedReference

原子操作类

在这里插入图片描述
在这里插入图片描述

分类

  • 基本类型原子类

  • 数组类型原子类

  • 引用类型原子类

  • 对象的属性修改原子类

  • 原子操作增强类原理

  • 基本类型原子类
    AtomicIntegerAtomicBooleanAtomicLong
    CountDownLatch保证所有线程操作完成之后再读取结果。

CountDownLatch countDownLatch = new CountDownLatch(Size);
try{
}finally{
countDownLatch.caountDown();
}
countDownLatch.await();
  • 数组类型原子类
    AtomicIntegerArrayAtomicReferenceArrayAtomicLongArray
    在这里插入图片描述
  • 引用类型原子类
    AtomicReferenceAtomicStampedReferenceAtomicMarkableReference
    AtomicStampedReference携带版本号的引用类型原子类,解决修改过几次。
    AtomicMarkableReference原子更新带有标记位的引用类型对象,解决是否修改过(false–true)(一次性的)
  • 对象的属性修改原子类
    AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdaterAtomicLongFieldUpdater`
    AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值。基于反射的实用程序,可对指定类的指定volatile int字段进行原子更新
    目的:以一种线程安全的方式操作非线程安全对象内的某些字段
    使用要求
    • 更新的对象属性必须使用 public volatile 修饰符。
    • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater)创建一个更新器,并且需要设置想要更新的类和属性
class BankAccount{
    String bankName="hhh";
    public volatile int money =0;
    public void  add(){
        money++;
    }
    AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
    public void transMoney(BankAccount bankAccount){
        fieldUpdater.getAndIncrement(bankAccount);
    }
}
public class CASDemo {
    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 1; i <=10; i++) {
            new Thread(()->{
                try{
                    for (int j = 1; j <= 1000; j++) {
//                        bankAccount.add();
                        bankAccount.transMoney(bankAccount);
                    }
                }finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t"+"result:"+bankAccount.money);
    }
}

在这里插入图片描述

  • 原子操作增强类原理
    DoubleAccumulatorDoubleAdderLongAccumulatorLongAdder

LongAdder: 只能用于加法,而且初始值必须为0. 当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong 。在低更新争用下,这两个类具有相似的特征。但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
LongAccumulator:提供了自定义的函数方法。自定义的。
在这里插入图片描述

LongAdder为什么快

  • 有多个窗口,分散热点。LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
  • sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
  • LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零分散热点的做法,用空间换时,用一个数组cels,将一个value拆分进这个数组cels。多个线程需要同时对value进行操作时候,可以对线和id进行hash得到hash值,再根据hash值映射到这个数组cels的某个下标,再对该下标所对应的值进行自增换作。当所有线程操作完毕。将数组cels的所有值和base都加起来作最终结果
    在这里插入图片描述

在这里插入图片描述
striped64比较重要的成员函数
在这里插入图片描述
在这里插入图片描述
源码

    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
        //是否竞争
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

二者区别
在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值