zoukankan      html  css  js  c++  java
  • cas

    是什么

    	CAS的全称为Compare-And-Swap,它是一条CPU并发原语,中文翻译成比较并交换,实现并发算法时常用到的一种技术,它包含三个操作数——内存位置、预期原值及更新值。执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功,这个过程是原子的。
    

    ​ 由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

    原理

    CAS (CompareAndSwap) CAS有3个操作数,内存位置值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来 。

    demo

    public class CASdemo {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger(1);
            System.out.println(atomicInteger.compareAndSet(1, 2)+ "   "+ atomicInteger.get());
            System.out.println(atomicInteger.compareAndSet(1, 3)+ "   "+ atomicInteger.get());
        }
    }
    
    		compareAndSet 源码
       /**
         *  this:表示要操作的对象
    				valueOffset:表示要操作对象中属性地址的偏移量
    				expect:表示需要修改数据的期望的值
    				update:表示需要修改为的新值
         * @param args
         */
    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    

    unsafe 是什么

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

    实现类

    AtomicInteger,AtomicBoolean,AtomicReference等

    自旋锁

    可以利用cas的原理自定义一个简版的自旋锁

    public class SpinlockDemo {
        AtomicReference<Thread> atomicReference = new AtomicReference();
        public void lock () {
            System.out.println(Thread.currentThread().getName()+"进来了");
            while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
            }
            System.out.println(Thread.currentThread().getName()+"抢到锁了");
        }
        public void unLock () {
            atomicReference.compareAndSet(Thread.currentThread(), null);
            System.out.println(Thread.currentThread().getName()+"开始释放锁");
        }
    
        public static void main(String[] args) throws InterruptedException {
            SpinlockDemo spinlockDemo = new SpinlockDemo();
            new Thread(()->{
                spinlockDemo.lock();
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
    
                }
                spinlockDemo.unLock();
            },"t1").start();
            TimeUnit.SECONDS.sleep(2);
            new Thread(()->{
                spinlockDemo.lock();
                spinlockDemo.unLock();
            },"t2").start();
        }
    }
    

    缺点

    1. 如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

    2. 会引发ABA问题

      ABA问题的解决办法:带版本号的原子引用 AtomicStampedReference

    package com.example.juc;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * @author yeric
     * @description:
     * @date 2021/9/28 23:11
     */
    public class ABADemo {
        /**
         * 普通的原子引用包装类
         */
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    
        // 传递两个值,一个是初始值,一个是初始版本号
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
    
        public static void main(String[] args) {
    
            System.out.println("============以下是ABA问题的产生==========");
    
            new Thread(() -> {
                // 把100 改成 101 然后在改成100,也就是ABA
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    // 睡眠一秒,保证t1线程,完成了ABA操作
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 把100 改成 101 然后在改成100,也就是ABA
                System.out.println(atomicReference.compareAndSet(100, 2019) + "	" + atomicReference.get());
    
            }, "t2").start();
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
            System.out.println("============以下是ABA问题的解决==========");
    
            new Thread(() -> {
    
                // 获取版本号
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	 第一次版本号" + stamp);
    
                // 暂停t3一秒钟
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 传入4个值,期望值,更新值,期望版本号,更新版本号
                atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
                        atomicStampedReference.getStamp() + 1);
    
                System.out.println(Thread.currentThread().getName() + "	 第二次版本号" + atomicStampedReference.getStamp());
    
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),
                        atomicStampedReference.getStamp() + 1);
    
                System.out.println(Thread.currentThread().getName() + "	 第三次版本号" + atomicStampedReference.getStamp());
    
            }, "t3").start();
    
            new Thread(() -> {
    
                // 获取版本号
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	 第一次版本号" + stamp);
    
                // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
    
                System.out.println(Thread.currentThread().getName() + "	 修改成功否:" + result + "	 当前最新实际版本号:"
                        + atomicStampedReference.getStamp());
    
                System.out.println(Thread.currentThread().getName() + "	 当前实际最新值" + atomicStampedReference.getReference());
    
            }, "t4").start();
    
        }
    }
    
  • 相关阅读:
    自定义 spark transformer 和 estimator 的范例
    spark 与 scikit-learn 机器学习流程组件设计哲学比较
    命名空间和作用域
    FeatureUnion 与 ColumnTransformer 关系
    注解与装饰器
    装饰器编写--要点
    闭包结构的本质
    SQL 自动增长 identity
    SQL 基本的函数
    int和long long有符号整形 负数比正数多一个
  • 原文地址:https://www.cnblogs.com/yerikm/p/15350703.html
Copyright © 2011-2022 走看看