zoukankan      html  css  js  c++  java
  • java高并发系列

    这是java高并发系列第32篇文章。

    java环境:jdk1.8。

    本文主要内容

    1. 4种方式实现计数器功能,对比其性能
    2. 介绍LongAdder
    3. 介绍LongAccumulator

    需求:一个jvm中实现一个计数器功能,需保证多线程情况下数据正确性。

    我们来模拟50个线程,每个线程对计数器递增100万次,最终结果应该是5000万。

    我们使用4种方式实现,看一下其性能,然后引出为什么需要使用LongAdderLongAccumulator

    方式一:synchronized方式实现

    package com.itsoku.chat32;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.LongAccumulator;
    
    /**
     * 跟着阿里p7学并发,微信公众号:javacode2018
     */
    public class Demo1 {
        static int count = 0;
    
        public static synchronized void incr() {
            count++;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            for (int i = 0; i < 10; i++) {
                count = 0;
                m1();
            }
        }
    
        private static void m1() throws InterruptedException {
            long t1 = System.currentTimeMillis();
            int threadCount = 50;
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 1000000; j++) {
                            incr();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
            long t2 = System.currentTimeMillis();
            System.out.println(String.format("结果:%s,耗时(ms):%s", count, (t2 - t1)));
        }
    }
    

    输出:

    结果:50000000,耗时(ms):1437
    结果:50000000,耗时(ms):1913
    结果:50000000,耗时(ms):386
    结果:50000000,耗时(ms):383
    结果:50000000,耗时(ms):381
    结果:50000000,耗时(ms):382
    结果:50000000,耗时(ms):379
    结果:50000000,耗时(ms):379
    结果:50000000,耗时(ms):392
    结果:50000000,耗时(ms):384
    

    平均耗时:390毫秒

    方式2:AtomicLong实现

    package com.itsoku.chat32;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.AtomicLong;
    
    /**
     * 跟着阿里p7学并发,微信公众号:javacode2018
     */
    public class Demo2 {
        static AtomicLong count = new AtomicLong(0);
    
        public static void incr() {
            count.incrementAndGet();
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            for (int i = 0; i < 10; i++) {
                count.set(0);
                m1();
            }
        }
    
        private static void m1() throws InterruptedException {
            long t1 = System.currentTimeMillis();
            int threadCount = 50;
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 1000000; j++) {
                            incr();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
            long t2 = System.currentTimeMillis();
            System.out.println(String.format("结果:%s,耗时(ms):%s", count, (t2 - t1)));
        }
    }
    

    输出:

    结果:50000000,耗时(ms):971
    结果:50000000,耗时(ms):915
    结果:50000000,耗时(ms):920
    结果:50000000,耗时(ms):923
    结果:50000000,耗时(ms):910
    结果:50000000,耗时(ms):916
    结果:50000000,耗时(ms):923
    结果:50000000,耗时(ms):916
    结果:50000000,耗时(ms):912
    结果:50000000,耗时(ms):908
    

    平均耗时:920毫秒

    AtomicLong内部采用CAS的方式实现,并发量大的情况下,CAS失败率比较高,导致性能比synchronized还低一些。并发量不是太大的情况下,CAS性能还是可以的。

    AtomicLong属于JUC中的原子类,还不是很熟悉的可以看一下:JUC中原子类,一篇就够了

    方式3:LongAdder实现

    先介绍一下LongAdder,说到LongAdder,不得不提的就是AtomicLong,AtomicLong是JDK1.5开始出现的,里面主要使用了一个long类型的value作为成员变量,然后使用循环的CAS操作去操作value的值,并发量比较大的情况下,CAS操作失败的概率较高,内部失败了会重试,导致耗时可能会增加。

    LongAdder是JDK1.8开始出现的,所提供的API基本上可以替换掉原先的AtomicLong。LongAdder在并发量比较大的情况下,操作数据的时候,相当于把这个数字分成了很多份数字,然后交给多个人去管控,每个管控者负责保证部分数字在多线程情况下操作的正确性。当多线程访问的时,通过hash算法映射到具体管控者去操作数据,最后再汇总所有的管控者的数据,得到最终结果。相当于降低了并发情况下锁的粒度,所以效率比较高,看一下下面的图,方便理解:

    代码:

    package com.itsoku.chat32;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.AtomicLong;
    import java.util.concurrent.atomic.LongAdder;
    
    /**
     * 跟着阿里p7学并发,微信公众号:javacode2018
     */
    public class Demo3 {
        static LongAdder count = new LongAdder();
    
        public static void incr() {
            count.increment();
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            for (int i = 0; i < 10; i++) {
                count.reset();
                m1();
            }
        }
    
        private static void m1() throws ExecutionException, InterruptedException {
            long t1 = System.currentTimeMillis();
            int threadCount = 50;
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 1000000; j++) {
                            incr();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
            long t2 = System.currentTimeMillis();
            System.out.println(String.format("结果:%s,耗时(ms):%s", count.sum(), (t2 - t1)));
        }
    }
    

    输出:

    结果:50000000,耗时(ms):206
    结果:50000000,耗时(ms):105
    结果:50000000,耗时(ms):107
    结果:50000000,耗时(ms):107
    结果:50000000,耗时(ms):105
    结果:50000000,耗时(ms):99
    结果:50000000,耗时(ms):106
    结果:50000000,耗时(ms):102
    结果:50000000,耗时(ms):106
    结果:50000000,耗时(ms):102
    

    平均耗时:100毫秒

    代码中new LongAdder()创建一个LongAdder对象,内部数字初始值是0,调用increment()方法可以对LongAdder内部的值原子递增1。reset()方法可以重置LongAdder的值,使其归0。

    方式4:LongAccumulator实现

    LongAccumulator介绍

    LongAccumulator是LongAdder的功能增强版。LongAdder的API只有对数值的加减,而LongAccumulator提供了自定义的函数操作,其构造函数如下:

    /**
      * accumulatorFunction:需要执行的二元函数(接收2个long作为形参,并返回1个long)
      * identity:初始值
     **/
    public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) {
    	this.function = accumulatorFunction;
    	base = this.identity = identity;
    }
    

    示例代码:

    package com.itsoku.chat32;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.LongAccumulator;
    import java.util.concurrent.atomic.LongAdder;
    
    /**
     * 跟着阿里p7学并发,微信公众号:javacode2018
     */
    public class Demo4 {
        static LongAccumulator count = new LongAccumulator((x, y) -> x + y, 0L);
    
        public static void incr() {
            count.accumulate(1);
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            for (int i = 0; i < 10; i++) {
                count.reset();
                m1();
            }
        }
    
        private static void m1() throws ExecutionException, InterruptedException {
            long t1 = System.currentTimeMillis();
            int threadCount = 50;
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 1000000; j++) {
                            incr();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
            long t2 = System.currentTimeMillis();
            System.out.println(String.format("结果:%s,耗时(ms):%s", count.longValue(), (t2 - t1)));
        }
    }
    

    输出:

    结果:50000000,耗时(ms):138
    结果:50000000,耗时(ms):111
    结果:50000000,耗时(ms):111
    结果:50000000,耗时(ms):103
    结果:50000000,耗时(ms):103
    结果:50000000,耗时(ms):105
    结果:50000000,耗时(ms):101
    结果:50000000,耗时(ms):106
    结果:50000000,耗时(ms):102
    结果:50000000,耗时(ms):103
    

    平均耗时:100毫秒

    LongAccumulator的效率和LongAdder差不多,不过更灵活一些。

    调用new LongAdder()等价于new LongAccumulator((x, y) -> x + y, 0L)

    从上面4个示例的结果来看,LongAdder、LongAccumulator全面超越同步锁及AtomicLong的方式,建议在使用AtomicLong的地方可以直接替换为LongAdder、LongAccumulator,吞吐量更高一些。

    java高并发系列目录

    1. 第1天:必须知道的几个概念
    2. 第2天:并发级别
    3. 第3天:有关并行的两个重要定律
    4. 第4天:JMM相关的一些概念
    5. 第5天:深入理解进程和线程
    6. 第6天:线程的基本操作
    7. 第7天:volatile与Java内存模型
    8. 第8天:线程组
    9. 第9天:用户线程和守护线程
    10. 第10天:线程安全和synchronized关键字
    11. 第11天:线程中断的几种方式
    12. 第12天JUC:ReentrantLock重入锁
    13. 第13天:JUC中的Condition对象
    14. 第14天:JUC中的LockSupport工具类,必备技能
    15. 第15天:JUC中的Semaphore(信号量)
    16. 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
    17. 第17天:JUC中的循环栅栏CyclicBarrier的6种使用场景
    18. 第18天:JAVA线程池,这一篇就够了
    19. 第19天:JUC中的Executor框架详解1
    20. 第20天:JUC中的Executor框架详解2
    21. 第21天:java中的CAS,你需要知道的东西
    22. 第22天:JUC底层工具类Unsafe,高手必须要了解
    23. 第23天:JUC中原子类,一篇就够了
    24. 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
    25. 第25天:掌握JUC中的阻塞队列
    26. 第26篇:学会使用JUC中常见的集合,常看看!
    27. 第27天:实战篇,接口性能提升几倍原来这么简单
    28. 第28天:实战篇,微服务日志的伤痛,一并帮你解决掉
    29. 第29天:高并发中常见的限流方式
    30. 第30天:JUC中工具类CompletableFuture,必备技能
    31. 第31天:获取线程执行结果,这6种方法你都知道?

    阿里p7一起学并发,公众号:路人甲java,每天获取最新文章!

  • 相关阅读:
    linux权限补充:rwt rwT rws rwS 特殊权限
    关于Linux操作系统下文件特殊权限的解释
    Java学习笔记——Java程序运行超时后退出或进行其他操作的实现
    Java实现 蓝桥杯 算法提高 判断名次
    Java实现 蓝桥杯 算法提高 判断名次
    Java实现 蓝桥杯 算法提高 日期计算
    Java实现 蓝桥杯 算法提高 日期计算
    Java实现 蓝桥杯 算法提高 概率计算
    Java实现 蓝桥杯 算法提高 概率计算
    Java实现 蓝桥杯 算法提高 复数四则运算
  • 原文地址:https://www.cnblogs.com/itsoku123/p/11424473.html
Copyright © 2011-2022 走看看