• 【JUC】7.原子类


    1. 什么是原子类

    原子类是指java.util.concurrent.atomic下的类

    在这里插入图片描述

    其中这十六个类可以分为以下

    • 基本类型原子类
    • 数据类型原子类
    • 引用类型原子类
    • 对象的属性修改原子类
    • 原子操作增强类

    1.1 基本类型原子类

    基本类型原子类包括

    • AtomicInteger
    • AtomicBoolean
    • AtomicLong

    相关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)
    class MyNumber{
        AtomicInteger atomicInteger = new AtomicInteger();
    
        public void addPlusPlus(){
            atomicInteger.getAndIncrement();
        }
    }
    
    
    public class AtomicIntegerDemo {
    
        public static final int SIZE = 50;
    
        @SneakyThrows
        public static void main(String[] args) {
            MyNumber myNumber = new MyNumber();
            CountDownLatch countDownLatch = new CountDownLatch(SIZE);
    
            for (int i = 0; i < SIZE; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 1000; j++) {
                            myNumber.addPlusPlus();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }, String.valueOf(i)).start();
            }
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + "\t" + "result:" + myNumber.atomicInteger.get());
        }
    }
    
    • 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

    在这里插入图片描述

    这里CountDownLatch是用来计算线程是否完成的


    1.2 数组类型原子类

    数组类型原子类包括以下

    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray

    这种类型比较简单,上当与将基本类型原子类进一步变成数组

    public class AtomicIntegerArrayDemo {
        public static void main(String[] args) {
            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));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述


    1.3 引用类型原子类

    引用类型原子类包括以下

    • AtomicReference
    • AtomicStampedReference
      • 携带版本号的引用类型原子类,可以解决ABA问题
      • 解决修改过几次
      • 状态戳原子引用
    • AtomicMarkableReference
      • 原子更新带有标记位的引用类型对象
      • 解决是否修改过
        • 它的定义是将状态戳简化为true|false
      • 状态戳原子引用

    1.4 对象的属性修改原子类

    • AtomicIntegerFieldUpdater:原子更新对象中的int类型字段的值
    • AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
    • AtomicReferenceFieldUpdater:原子更新引用类型字段的值
    class BankAccount {
        String bandName = "CCB";
    
        public volatile int money = 0;
    
        AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
    
        public void transMoney(BankAccount bankAccount) {
            fieldUpdater.getAndIncrement(bankAccount);
        }
    }
    
    
    public class AtomicReferenceFieldUpdaterDemo {
        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 = 0; j < 1000; j++) {
                            bankAccount.transMoney(bankAccount);
                        }
                    }finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + "\t" + "result:" + bankAccount.money);
        }
    }
    
    • 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
    class MyVar{
        public volatile Boolean isInit = Boolean.FALSE;
        AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater =
                AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
        @SneakyThrows
        public void init(MyVar myVar){
            if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)){
                System.out.println(Thread.currentThread().getName() + "\t" + "------ start init, need 2 s");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t" + "====== over init");
            }else {
                System.out.println(Thread.currentThread().getName() + "\t" + "====== 已有线程初始化");
            }
        }
    }
    
    public class AtomicReferenceFieldUpdaterDemo {
        public static void main(String[] args) {
            MyVar myVar = new MyVar();
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    myVar.init(myVar);
                }, String.valueOf(i)).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

    在这里插入图片描述


    1.5 原子操作增强类

    原子操作增强类是指:

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

    在这里插入图片描述

    原子操作类是JDK1.8出现的

    在阿里巴巴Java开发手册中提到:如果是count++操作,使用如下类实现AtomicInteger count = new AtomicInteger();

    count.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,这比AtomicLong性能更好(减少乐观锁)

    这类通常是最好的AtomicLong当多个线程共同更新和用于用途,如收集统计,不为细粒度的同步控制。在低更新争用下,两个类具有相似的特征。但在高争用,预计这一类的吞吐量显着更高,在更高的空间消耗的费用。

    就以LongAdder而言,常用API如下

    方法名说明
    void add(long x)将当前的value加x
    void increment()将当前的value加1
    void decrement()将当前的value减1
    long sum()返回当前值,特别注意,在没有并发更新value的情况下,sum会返回一个精确值,存在并发情况下,sum不保证返回精确值
    void reset()将value重置为0,可用于替代重新new一个LongAdder,但此方法只可以在没有并发更新的情况下
    long sumThenReset()获取当前value,并将value重置为0
    public class LongAdderAPIDemo {
        public static void main(String[] args) {
            LongAdder longAdder = new LongAdder();
    
            //1+1
            longAdder.increment();
            //1+1+1
            longAdder.increment();
            //1+1+1+1
            longAdder.increment();
            //1+1+1+1+1
            longAdder.increment();
            System.out.println(longAdder.sum());
    
            //5代表初始值,第一个参数代表模式
            LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 5);
            //1 + 5
            longAccumulator.accumulate(1);
            //1 + 5 + 4
            longAccumulator.accumulate(4);
            System.out.println(longAccumulator.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    模拟高并发点赞

    class ClickNumber {
        /**
         * 使用重量级锁synchronized
         */
        int number = 0;
    
        public synchronized void clickBySynchronized() {
            number++;
        }
    
        /**
         * 使用atomicLong
         */
        AtomicLong atomicLong = new AtomicLong(0);
    
        public void clickByAtomicLong() {
            atomicLong.getAndIncrement();
        }
    
        /**
         * longAdder默认从0开始
         */
        LongAdder longAdder = new LongAdder();
    
        public void clickByLongAdder() {
            // + 1
            longAdder.increment();
        }
    
        /**
         * 规定为x + y
         * 从0开始
         */
        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
    
        public void clickByLongAccumulator() {
            longAccumulator.accumulate(1);
        }
    
    }
    
    /**
     * 50个线程,每个线程100w次,总点赞数出来
     */
    public class AccumulatorCompareDemo {
        public static final int _1W = 10000;
        public static final int threadNumber = 50;
    
        public static void main(String[] args) throws InterruptedException {
            ClickNumber clickNumber = new ClickNumber();
    
            CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
            CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
            CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
            CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
    
            long starTime, endTime;
            starTime = System.currentTimeMillis();
            for (int i = 0; i < threadNumber; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 100 * _1W; j++) {
                            clickNumber.clickBySynchronized();
                        }
                    } finally {
                        countDownLatch1.countDown();
                    }
                }, String.valueOf(i)).start();
            }
            countDownLatch1.await();
            endTime = System.currentTimeMillis();
            System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickBySynchronized: " + clickNumber.number);
    
            starTime = System.currentTimeMillis();
            for (int i = 0; i < threadNumber; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 100 * _1W; j++) {
                            clickNumber.clickByAtomicLong();
                        }
                    } finally {
                        countDownLatch2.countDown();
                    }
                }, String.valueOf(i)).start();
            }
            countDownLatch2.await();
            endTime = System.currentTimeMillis();
            System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickByAtomicLong: " + clickNumber.atomicLong.get());
    
    
            starTime = System.currentTimeMillis();
            for (int i = 0; i < threadNumber; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 100 * _1W; j++) {
                            clickNumber.clickByLongAdder();
                        }
                    } finally {
                        countDownLatch3.countDown();
                    }
                }, String.valueOf(i)).start();
            }
            countDownLatch3.await();
            endTime = System.currentTimeMillis();
            System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickByLongAdder: " + clickNumber.longAdder.sum());
    
            starTime = System.currentTimeMillis();
            for (int i = 0; i < threadNumber; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 100 * _1W; j++) {
                            clickNumber.clickByLongAccumulator();
                        }
                    } finally {
                        countDownLatch4.countDown();
                    }
                }, String.valueOf(i)).start();
            }
            countDownLatch4.await();
            endTime = System.currentTimeMillis();
            System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickBySynchronized: " + clickNumber.longAccumulator.get());
    
        }
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    在这里插入图片描述

    通过这个案例可以发现,LongAdder的性能更快


    2. LongAdder底层原理

    为什么LongAdder更快呢?

    前面说到AtomicLong是通过CAS实现的,当线程数少的的话,通过自旋锁等待解决

    LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

    sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前Atomicl.ong一个value的更新压力分散到多个value中去,从而降级更新热点。

    LongAdder的base变量:低并发,直接累加到该变量上

    Cell[]数组:高并发,累加进各个线程自己的槽


  • 相关阅读:
    ADO.NET组件学习笔记
    可编程计数器/定时器8253
    Oracle 平均数详解(avg)
    Kubernetes——部署应用到集群中
    【LeetCode-简答题】242. 有效的字母异位词
    Golang 依赖注入设计哲学|12.6K 的依赖注入库 wire
    百度paddleocr检测训练
    通过电商API接口,代购系统可以获取到商品、订单、物流等多种信息
    Antdv+Asp.net WebApi开发学生信息管理系统(一)
    【数字IC验证快速入门】3、数字IC设计全流程介绍
  • 原文地址:https://blog.csdn.net/weixin_51146329/article/details/127405896