• CAS与原子类


    CAS

    CAS是什么?

    compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数——内存位置预期原值更新值

    • 执行CAS操作的时候,将内存位置的值与预期原值比较:
    • 如果相匹配,那么处理器会自动将该位置值更新为新值;
    • 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。

    CAS如何保证并发原子操作?

    • CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
    • CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
    • 执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现的,其实在这一点上还是有排他锁的,只是比起用synchronized, 这里的排他时间要短的多, 所以在多线程情况下性能会比较好。

    CAS底层原理UnSafe?

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

    CAS引出来ABA问题???

    • CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
    • 比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功
    • 尽管线程one的CAS操作成功,但是不代表这个过程(被修改过了)就是没有问题的。
    • 示例代码如下
    /**
     * ABA问题,两边没有问题,但中间被修改过。
     */
    public class ABATest01 {
    
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger(10);//初始值10
    
            new Thread(()->{
                System.out.println("A: "+atomicInteger.get());
                atomicInteger.compareAndSet(10,2022); //将值更新为2022
    
                System.out.println("B: "+atomicInteger.get());
    
                atomicInteger.compareAndSet(2022,10); //将值更新为10
                System.out.println("A: "+atomicInteger.get());
            },"t1").start();
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{
                System.out.println("A: "+atomicInteger.get());//10值没变
                atomicInteger.compareAndSet(10,2022);//将值更新为2022
                System.out.println("B: "+atomicInteger.get());//2022
                //虽然更新成功,但中间却被修改过
            },"t1").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    解决方法
    • 版本号
    • 时间戳
    /**
     * 解决:版本号,时间戳
     */
    public class ABATest02 {
    
        static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<Integer>(10,1);
        public static void main(String[] args) {
    
            new Thread(()->{
                System.out.println("t1 第一次:"+stampedReference.getReference()+"\t"+stampedReference.getStamp());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stampedReference.compareAndSet(10,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println("t1 第一次修改完:"+stampedReference.getReference()+"\t"+stampedReference.getStamp());
    
                stampedReference.compareAndSet(100, 10, stampedReference.getStamp(), stampedReference.getStamp()+1);
                System.out.println("t1 第二次修改完:"+stampedReference.getReference()+"\t"+stampedReference.getStamp());
            },"t1").start();
    
    
            new Thread(()->{
                int stamp=stampedReference.getStamp();
                System.out.println("t2 第一次:"+stampedReference.getReference()+"\t"+stamp);
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
                System.out.println("t2 第一次修改情况:"+b);
                System.out.println("t2 第一次修改完:"+stampedReference.getReference()+"\t"+stampedReference.getStamp());
            },"t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    借鉴CAS思想,实现自旋锁

    • 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
    /**
     * 利用CAS原子引用完成自旋锁
     */
    public class SpinLockTest {
    
        AtomicReference<Thread> atomicReference=new AtomicReference<Thread>();
    
        public void lock(){//加锁
            Thread thread = Thread.currentThread();
            while (!atomicReference.compareAndSet(null,thread)){}
        }
        public void unlock(){//解锁
            Thread thread = Thread.currentThread();
            while (!atomicReference.compareAndSet(thread,null)){}
        }
    
        public static void main(String[] args) {
            SpinLockTest spinLock = new SpinLockTest();
            new Thread(()->{
                spinLock.lock();
                System.out.println("t1 come in...");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                spinLock.unlock();
                System.out.println("t1 unlock in...");
            },"t1").start();
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                spinLock.lock();
                System.out.println("t2 come in...");
                spinLock.unlock();
                System.out.println("t2 unlock in...");
            },"t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    原子类

    java.util.concurrent.atomic 包下的类
    在这里插入图片描述

    基本类型原子类

    常用API简介
    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
    boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    数组类型原子类

    • AtomicIntegerArray

    • AtomicLongArray

    • AtomicReferenceArray

    public class AtomicIntegerArrayTest
    {
        public static void main(String[] args)
        {
            AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
            //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
            //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
    
            for (int i = 0; i <atomicIntegerArray.length(); i++) {
                System.out.println(atomicIntegerArray.get(i));
            }
            System.out.println();
            System.out.println();
            System.out.println();
            int tmpInt = 0;
    
            tmpInt = atomicIntegerArray.getAndSet(0,1122);
            System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
            atomicIntegerArray.getAndIncrement(1);
            atomicIntegerArray.getAndIncrement(1);
            tmpInt = atomicIntegerArray.getAndIncrement(1);
            System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    引用类型原子类

    • AtomicReference
    • AtomicStampedReference
      携带版本号的引用类型原子类,可以解决ABA问题
      解决修改过几次
      状态戳原子引用
    • AtomicMarkableReference
      原子更新带有标记位的引用类型对象
      解决是否修改过 它的定义就是将状态戳简化为true|false – 类似一次性筷子
      状态戳(true/false)原子引用
    • 示例代码看自旋锁实现(上面)

    对象的属性修改原子类

    • AtomicIntegerFieldUpdater,原子更新对象中int类型字段的值
    • AtomicLongFieldUpdater,原子更新对象中Long类型字段的值
    • AtomicReferenceFieldUpdater,原子更新引用类型字段的值
    使用要求
    • 更新的对象属性必须使用 public volatile 修饰符。

    • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

    /**
     * 原子 数值类型 属性字段修改
     */
    public class AtomicIntegerFieldUpdaterTest {
    
        private static final int SIZE=50;
        public static void main(String[] args) {
            Account account = new Account();
            CountDownLatch countDownLatch = new CountDownLatch(SIZE);
            for (int i = 0; i < SIZE; i++) {
                new Thread(()->{
                    try {
                        for (int j = 0; j <1000 ; j++) {
                            account.transfer(account);
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(account.money);
        }
    }
    
    class Account{
        public volatile int money=0;
        AtomicIntegerFieldUpdater<Account> updater=
                AtomicIntegerFieldUpdater.newUpdater(Account.class,"money");
    
        public void transfer(Account account){
            updater.incrementAndGet(account);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    原子操作增强类

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder
    LongAdder,高性能 累加器
    **
     * 高性能 累加器
     */
    public class LongAdderTest {
    
        public static void main(String[] args) throws InterruptedException {
            LongAdder longAdder = new LongAdder();
    
            CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i <1000 ; i++) {
                new Thread(()->{
                    longAdder.increment();
                    countDownLatch.countDown();
                }).start();
            }
            countDownLatch.await();
            longAdder.decrement();
            System.out.println(longAdder.sum());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    LongAdder,高性能 计算
    /**
     * 高性能 计算
     */
    public class LongAccumulatorTest {
    
        public static void main(String[] args) throws InterruptedException {
            LongAccumulator accumulator = new LongAccumulator((x,y)->x+y,0);
    
            CountDownLatch countDownLatch = new CountDownLatch(1000);
            for (int i = 0; i <1000 ; i++) {
                new Thread(()->{
                    accumulator.accumulate(1);
                    countDownLatch.countDown();
                }).start();
            }
            countDownLatch.await();
            System.out.println(accumulator.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    思科模拟器
    《Mybatis 手撸专栏》第9章:细化XML语句构建器,完善静态SQL解析
    ubuntu安装 miniconda 同步时间
    Windows虚拟机部署Docker
    Mysql各种锁
    壳聚糖-聚乙二醇-CY7,Cy7-PEG-Chitosan,CY7-壳聚糖
    高并发应用实践——缓存简介
    FPGA实现精简版UDP通信,占资源很少但很稳定,提供2套工程源码
    PTA题目 谁先倒
    读像火箭科学家一样思考笔记05_思想实验
  • 原文地址:https://blog.csdn.net/qq_54429571/article/details/127714656