• JUC-原子操作类之18罗汉增强


    原子操作类

    Java中有那么一些类,是以Atomic开头的。这一系列的类我们称之为原子操作类。以最简单的类AtomicInteger为例。它相当于一个int变量,我们执行Int的 i++ 的时候并不是一个原子操作。而使用AtomicInteger的incrementAndGet却能保证原子操作。 更新变量这种场景下效果和 synchronized 相同,却要简单高效的多。

    AtomicBoolean
    AtomicInteger
    AtomicIntegerArray
    AtomicIntegerFieldUpdater
    AtomicLong
    AtomicLongArray
    AtomicLongFieldUpdater
    AtomicMarkableReference
    AtomicReference
    AtomicReferenceArray
    AtomicReferenceFieldUpdater
    AtomicStampedReference
    DoubleAccumulator
    DoubleAdder
    LongAccumulator
    LongAdder

    一原子更新基本类型

    • AtomicInteger
    • AtomicBoolean
    • AtomicLong

    1.常用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
    • 7

    tosleep→countDownLatch
    主线程需要等待多个组件加载完毕,之后再继续执行。因为无法法准确判断,使用toSleep无法准确设置线程睡眠时间导致很大问题。countDownLatch则可以使用此问题

    class MyNumber
    {
        @Getter
        private AtomicInteger atomicInteger = new AtomicInteger();
        public void addPlusPlus()
        {
            atomicInteger.incrementAndGet();
        }
    }
    
    /**
     * @auther zzyy
     * @create 2020-07-03 17:16
     */
    public class AtomicIntegerDemo
    {
        public static void main(String[] args) throws InterruptedException
        {
            MyNumber myNumber = new MyNumber();
            CountDownLatch countDownLatch = new CountDownLatch(100);
    
            for (int i = 1; i <=100; i++) {
                new Thread(() -> {
                    try
                    {
                        for (int j = 1; j <=5000; j++)
                        {
                            myNumber.addPlusPlus();
                        }
                    }finally {
                        countDownLatch.countDown();
                    }
                },String.valueOf(i)).start();
            }
    
            countDownLatch.await();
    
            System.out.println(myNumber.getAtomicInteger().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

    CountDownLatch概念

    CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
    CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

    CountDownLatch的用法

    1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
    2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

    二原子更新数组类型

    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
    public class AtomicIntegerArrayDemo
    {
        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

    @Getter
    @ToString
    @AllArgsConstructor
    class User
    {
        String userName;
        int    age;
    }
    
    public class AtomicReferenceDemo
    {
        public static void main(String[] args)
        {
            User z3 = new User("z3",24);
            User li4 = new User("li4",26);
    
            AtomicReference<User> atomicReferenceUser = new AtomicReference<>();
    
            atomicReferenceUser.set(z3);
            System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
            System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    自旋锁SpinLockDemo

    /**
     * 题目:实现一个自旋锁
     * 自旋锁好处:循环比较获取没有类似wait的阻塞。
     *
     * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
     * 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。
     */
    public class SpinLockDemo
    {
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        public void myLock()
        {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"\t come in");
            while(!atomicReference.compareAndSet(null,thread))
            {
    
            }
        }
    
        public void myUnLock()
        {
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread,null);
            System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
        }
    
        public static void main(String[] args)
        {
            SpinLockDemo spinLockDemo = new SpinLockDemo();
    
            new Thread(() -> {
                spinLockDemo.myLock();
                //暂停一会儿线程
                try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
                spinLockDemo.myUnLock();
            },"A").start();
            //暂停一会儿线程,保证A线程先于B线程启动并完成
            try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                spinLockDemo.myLock();
                spinLockDemo.myUnLock();
            },"B").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
    • 45
    • 46
    • 47
    • 48
    • 49

    AtomicStampedReference

    • 携带版本号的引用类型原子类,可以解决ABA问题
    • 解决修改过几次
    • 状态戳原子引用
    public class ABADemo
    {
        static AtomicInteger atomicInteger = new AtomicInteger(100);
        static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
    
        public static void main(String[] args)
        {
            abaProblem();
            abaResolve();
        }
    
        public static void abaResolve()
        {
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();
                System.out.println("t3 ----第1次stamp  "+stamp);
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
                System.out.println("t3 ----第2次stamp  "+atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                System.out.println("t3 ----第3次stamp  "+atomicStampedReference.getStamp());
            },"t3").start();
    
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();
                System.out.println("t4 ----第1次stamp  "+stamp);
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
                boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
            },"t4").start();
        }
    
        public static void abaProblem()
        {
            new Thread(() -> {
                atomicInteger.compareAndSet(100,101);
                atomicInteger.compareAndSet(101,100);
            },"t1").start();
    
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                atomicInteger.compareAndSet(100,20210308);
                System.out.println(atomicInteger.get());
            },"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
    • 45
    • 46
    • 47
    • 48
    • 49

    AtomicMarkableReference

    • 原子更新带有标记位的引用类型对象
    • 解决是否修改过 它的定义就是将状态戳简化为true|false – 类似一次性筷子
      状态戳(true/false)原子引用
    public class ABADemo
    {
        static AtomicInteger atomicInteger = new AtomicInteger(100);
        static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
        static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);
    
        public static void main(String[] args)
        {
            new Thread(() -> {
                atomicInteger.compareAndSet(100,101);
                atomicInteger.compareAndSet(101,100);
                System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
            },"t1").start();
    
            new Thread(() -> {
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                atomicInteger.compareAndSet(100,2020);
            },"t2").start();
    
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
    
            System.out.println(atomicInteger.get());
    
            System.out.println();
            System.out.println();
            System.out.println();
    
            System.out.println("============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================");
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+stampedReference.getStamp());
                //故意暂停200毫秒,让后面的t4线程拿到和t3一样的版本号
                try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
    
                stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+stampedReference.getStamp());
                stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+stampedReference.getStamp());
            },"t3").start();
    
            new Thread(() -> {
                int stamp = stampedReference.getStamp();
                System.out.println(Thread.currentThread().getName()+"\t =======1次版本号"+stamp);
                //暂停2秒钟,让t3先完成ABA操作了,看看自己还能否修改
                try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
                boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName()+"\t=======2次版本号"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
            },"t4").start();
    
            System.out.println();
            System.out.println();
            System.out.println();
    
            System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");
    
            new Thread(() -> {
                boolean marked = markableReference.isMarked();
                System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
                try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
                markableReference.compareAndSet(100,101,marked,!marked);
                System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+markableReference.isMarked());
                markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
                System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+markableReference.isMarked());
            },"t5").start();
    
            new Thread(() -> {
                boolean marked = markableReference.isMarked();
                System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
                //暂停几秒钟线程
                try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
                markableReference.compareAndSet(100,2020,marked,!marked);
                System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
            },"t6").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
    • 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

    四原子修改对象的属性

    • AtomicIntegerFieldUpdater
      • 原子更新对象中int类型字段的值
    • AtomicLongFieldUpdater
      • 原子更新对象中Long类型字段的值
    • AtomicReferenceFieldUpdater
      • 原子更新引用类型字段的值

    1、使用目的

    以一种线程安全的方式操作非线程安全对象内的某些字段

    2、使用要求

    更新的对象属性必须使用 public volatile 修饰符。

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

    3、AtomicIntegerFieldUpdater

    class BankAccount
    {
        private String bankName = "CCB";//银行
        public volatile int money = 0;//钱数
        AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
    
        //不加锁+性能高,局部微创
        public void transferMoney(BankAccount bankAccount)
        {
            accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
        }
    }
    
    /**
     * @auther zzyy
     * @create 2020-07-14 18:06
     * 以一种线程安全的方式操作非线程安全对象的某些字段。
     * 需求:
     * 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
     * 除了synchronized和CAS,还可以使用AtomicIntegerFieldUpdater来实现。
     */
    public class AtomicIntegerFieldUpdaterDemo
    {
    
        public static void main(String[] args)
        {
            BankAccount bankAccount = new BankAccount();
    
            for (int i = 1; i <=1000; i++) {
                int finalI = i;
                new Thread(() -> {
                    bankAccount.transferMoney(bankAccount);
                },String.valueOf(i)).start();
            }
    
            //暂停毫秒
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
    
            System.out.println(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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    4、AtomicReferenceFieldUpdater

    class MyVar
    {
        public volatile Boolean isInit = Boolean.FALSE;
        AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");
    
    
        public void init(MyVar myVar)
        {
            if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
            }else{
                System.out.println(Thread.currentThread().getName()+"\t"+"------其它线程正在初始化");
            }
        }
    
    
    }
    
    
    /**
     * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
     */
    public class AtomicIntegerFieldUpdaterDemo
    {
        public static void main(String[] args) throws InterruptedException
        {
            MyVar myVar = new MyVar();
    
            for (int i = 1; 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    五原子操作增强类原理解析

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

    【参考】volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。说明:如果是count-++操作,使用如下类实现:AtomicInteger count=new AtomicInteger(;count.addAndGet(1);如果是JDK8,推荐使用LongAdder对像,比AtomicLong性能更好(减少乐观锁的重试次数)。

    1、点赞计数器,看看性能

    方法名说明
    void add(long x)将当前的value加x。
    void increment()将当前的value加1。
    void decremen()将当前的value减1
    long sun()返回当前值。特别注意,在没有并发更新vlue的情况下,sum会返回一个精确值,在存在并long sumo发的情况下,sum不保证返回精确值。
    void reset()将value重置为O,可用于替代重新new一个LongAdder,.但此方法只可以在没有并发更新的void reseto情况下使用。
    long sumThenReset()获取当前value,并将value重置为0。

    LongAdder只能用来计算加法,且从零开始计算

    LongAccumulator提供了自定义的函数操作

    //long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等
    
    import java.util.concurrent.atomic.LongAccumulator;
    import java.util.concurrent.atomic.LongAdder;
    import java.util.function.LongBinaryOperator;
    
    public class LongAccumulatorDemo
    {
    
        LongAdder longAdder = new LongAdder();
        public void add_LongAdder()
        {
            longAdder.increment();
        }
    
        //LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y,0);
        LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
        {
            @Override
            public long applyAsLong(long left, long right)
            {
                return left - right;
            }
        },777);
    
        public void add_LongAccumulator()
        {
            longAccumulator.accumulate(1);
        }
    
        public static void main(String[] args)
        {
            LongAccumulatorDemo demo = new LongAccumulatorDemo();
    
            demo.add_LongAccumulator();
            demo.add_LongAccumulator();
            System.out.println(demo.longAccumulator.longValue());
        }
    }
    
    
    • 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

    2、LongAdderAPI

    public class LongAdderAPIDemo
    {
        public static void main(String[] args)
        {
            LongAdder longAdder = new LongAdder();
    
            longAdder.increment();
            longAdder.increment();
            longAdder.increment();
    
            System.out.println(longAdder.longValue());
    
            LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);
    
            longAccumulator.accumulate(1);
            longAccumulator.accumulate(2);
            longAccumulator.accumulate(3);
    
            System.out.println(longAccumulator.longValue());
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3、LongAdder高性能对比Code演示

    class ClickNumberNet
    {
        int number = 0;
        public synchronized void clickBySync()
        {
            number++;
        }
    
        AtomicLong atomicLong = new AtomicLong(0);
        public void clickByAtomicLong()
        {
            atomicLong.incrementAndGet();
        }
    
        LongAdder longAdder = new LongAdder();
        public void clickByLongAdder()
        {
            longAdder.increment();
        }
    
        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
        public void clickByLongAccumulator()
        {
            longAccumulator.accumulate(1);
        }
    }
    
    /**
     * @auther zzyy
     * @create 2020-05-21 22:23
     * 50个线程,每个线程100W次,总点赞数出来
     */
    public class LongAdderDemo2
    {
        public static void main(String[] args) throws InterruptedException
        {
            ClickNumberNet clickNumberNet = new ClickNumberNet();
    
            long startTime;
            long endTime;
            CountDownLatch countDownLatch = new CountDownLatch(50);
            CountDownLatch countDownLatch2 = new CountDownLatch(50);
            CountDownLatch countDownLatch3 = new CountDownLatch(50);
            CountDownLatch countDownLatch4 = new CountDownLatch(50);
    
    
            startTime = System.currentTimeMillis();
            for (int i = 1; i <=50; i++) {
                new Thread(() -> {
                    try
                    {
                        for (int j = 1; j <=100 * 10000; j++) {
                            clickNumberNet.clickBySync();
                        }
                    }finally {
                        countDownLatch.countDown();
                    }
                },String.valueOf(i)).start();
            }
            countDownLatch.await();
            endTime = System.currentTimeMillis();
            System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySync result: "+clickNumberNet.number);
    
            startTime = System.currentTimeMillis();
            for (int i = 1; i <=50; i++) {
                new Thread(() -> {
                    try
                    {
                        for (int j = 1; j <=100 * 10000; j++) {
                            clickNumberNet.clickByAtomicLong();
                        }
                    }finally {
                        countDownLatch2.countDown();
                    }
                },String.valueOf(i)).start();
            }
            countDownLatch2.await();
            endTime = System.currentTimeMillis();
            System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong result: "+clickNumberNet.atomicLong);
    
            startTime = System.currentTimeMillis();
            for (int i = 1; i <=50; i++) {
                new Thread(() -> {
                    try
                    {
                        for (int j = 1; j <=100 * 10000; j++) {
                            clickNumberNet.clickByLongAdder();
                        }
                    }finally {
                        countDownLatch3.countDown();
                    }
                },String.valueOf(i)).start();
            }
            countDownLatch3.await();
            endTime = System.currentTimeMillis();
            System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder result: "+clickNumberNet.longAdder.sum());
    
            startTime = System.currentTimeMillis();
            for (int i = 1; i <=50; i++) {
                new Thread(() -> {
                    try
                    {
                        for (int j = 1; j <=100 * 10000; j++) {
                            clickNumberNet.clickByLongAccumulator();
                        }
                    }finally {
                        countDownLatch4.countDown();
                    }
                },String.valueOf(i)).start();
            }
            countDownLatch4.await();
            endTime = System.currentTimeMillis();
            System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator result: "+clickNumberNet.longAccumulator.longValue());
    
    
        }
    }
    
    
    • 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

    4、使用总结

    AtomicLong

    • 线程安全,可允许一些性能损耗,要求高精度时可使用
    • 保证精度,性能代价
    • AtomicLong是多个线程针对单个热点值value进行原子操作

    LongAdder

    • 当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
    • 保证性能,精度代价
    • LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

    六总结

    1、AtomicLong

    原理

    • CAS+自旋
    • incrementAndGet

    场景

    • 低并发下的全局计算
    • AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。

    缺陷

    • 高并发后性能急剧下降
    • AtomicLong的自旋会成为瓶颈
    • N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。

    2、LongAdder

    原理

    • CAS+Base+Cell数组分散
    • 空间换时间并分散了热点数据

    场景

    • 高并发下的全局计算

    缺陷

    • sum求和后还有计算线程修改结果的话,最后结果不够准确
  • 相关阅读:
    【535. TinyURL 的加密与解密】
    UGUI性能优化学习笔记(一)网格重建
    信息学奥赛一本通2060:【例1.1】计算机输出
    数字化营销:企业营收N倍增长的秘诀
    ch58x/ch59xADC差分采样NTC电阻获取当前温度
    Cave Cows 3
    网络编程详细介绍()
    渗透测试nginx增加400,500报错页面
    大数据-数据分析初步学习,待补充
    【开题报告】基于SpringBoot的二手汽车交易平台的设计与实现
  • 原文地址:https://blog.csdn.net/weixin_51596697/article/details/127763024