• 异步性能不如同步?通过压测讨论应该如何设置线程数


    欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习

    1 代码实例

    1.1 A服务

    声明A服务提供五个方法:

    public class BizParamDTO {
        private String field;
    }
    
    public interface AService {
    
        public String a1(BizParamDTO param) throws Exception;
    
        public String a2(BizParamDTO param) throws Exception;
    
        public String a3(BizParamDTO param) throws Exception;
    
        public String a4(BizParamDTO param) throws Exception;
    
        public String a5(BizParamDTO param) throws Exception;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    a1-a4休眠100毫秒,a5休眠600毫秒:

    @Service
    public class AServiceImpl implements AService {
    
        @Override
        public String a1(BizParamDTO param) throws Exception {
            System.out.println(Thread.currentThread().getName() + ",a1 param=" + param);
            TimeUnit.MILLISECONDS.sleep(100);
            return param.getField();
        }
    
        @Override
        public String a2(BizParamDTO param) throws Exception {
            System.out.println(Thread.currentThread().getName() + ",a2 param=" + param);
            TimeUnit.MILLISECONDS.sleep(100);
            return param.getField();
        }
    
        @Override
        public String a3(BizParamDTO param) throws Exception {
            System.out.println(Thread.currentThread().getName() + ",a3 param=" + param);
            TimeUnit.MILLISECONDS.sleep(100);
            return param.getField();
        }
    
        @Override
        public String a4(BizParamDTO param) throws Exception {
            System.out.println(Thread.currentThread().getName() + ",a4 param=" + param);
            TimeUnit.MILLISECONDS.sleep(100);
            return param.getField();
        }
    
        @Override
        public String a5(BizParamDTO param) throws Exception {
            System.out.println(Thread.currentThread().getName() + ",a5 param=" + param);
            TimeUnit.MILLISECONDS.sleep(600);
            return param.getField();
        }
    }
    
    • 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

    1.2 B服务

    B服务同步调用A服务五个方法:

    public interface BService {
        public void b(BizParamDTO param) throws Exception;
    }
    
    @Service
    public class BServiceImpl implements BService {
        @Resource
        private AService aservice;
    
        @Override
        public void b(BizParamDTO param) throws Exception {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("taskB");
            String r1 = aservice.a1(param);
            String r2 = aservice.a2(param);
            String r3 = aservice.a3(param);
            String r4 = aservice.a4(param);
            String r5 = aservice.a5(param);
            List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
            stopWatch.stop();
            System.out.println("b1-costTime=" + stopWatch.getTotalTimeMillis() + "ms");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    1.3 C服务

    C服务通过异步调用A服务:

    public interface CService {
        public void c(BizParamDTO param) throws Exception;
    }
    
    @Service
    public class CServiceImpl implements CService {
        private final static Integer TYPE = TypeEnum.CPU.getCode();
    
        @Resource
        private AService aservice;
    
        @Override
        public void c(BizParamDTO param) throws Exception {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("taskC");
            Future<String> f1 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    StopWatch s = new StopWatch();
                    s.start();
                    String result = aservice.a1(param);
                    s.stop();
                    System.out.println("a1-costTime=" + s.getTotalTimeMillis() + "ms");
                    return result;
                }
            });
            Future<String> f2 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    StopWatch s = new StopWatch();
                    s.start();
                    String result = aservice.a2(param);
                    s.stop();
                    System.out.println("a2-costTime=" + s.getTotalTimeMillis() + "ms");
                    return result;
                }
            });
            Future<String> f3 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    StopWatch s = new StopWatch();
                    s.start();
                    String result = aservice.a3(param);
                    s.stop();
                    System.out.println("a3-costTime=" + s.getTotalTimeMillis() + "ms");
                    return result;
                }
            });
            Future<String> f4 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    StopWatch s = new StopWatch();
                    s.start();
                    String result = aservice.a4(param);
                    s.stop();
                    System.out.println("a4-costTime=" + s.getTotalTimeMillis() + "ms");
                    return result;
                }
            });
            Future<String> f5 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    StopWatch s = new StopWatch();
                    s.start();
                    String result = aservice.a5(param);
                    s.stop();
                    System.out.println("a5-costTime=" + s.getTotalTimeMillis() + "ms");
                    return result;
                }
            });
    
            // 等待结果
            StopWatch watch = new StopWatch("waitWatch");
            watch.start("f1.get()");
            String r1 = f1.get();
            watch.stop();
    
            watch.start("f2.get()");
            String r2 = f2.get();
            watch.stop();
    
            watch.start("f3.get()");
            String r3 = f3.get();
            watch.stop();
    
            watch.start("f4.get()");
            String r4 = f4.get();
            watch.stop();
    
            watch.start("f5.get()");
            String r5 = f5.get();
            watch.stop();
    
            // 输出结果
            List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
            stopWatch.stop();
            System.out.println("c1-costTime=" + stopWatch.getTotalTimeMillis() + "ms,costTimeDetail=" + watch.prettyPrint());
        }
    }
    
    • 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

    1.4 线程池

    我们把线程池划分为两种类型:

    public enum TypeEnum {
    
        IO(1, "IO密集"),
    
        CPU(2, "CPU密集")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    CPU密集型线程数:CPU数量+1

    IO密集型线程数:CPU数量除以(1-阻塞系数0.9)

    public class MyThreadFactory {
    
        /** 线程执行器 **/
        private static volatile ThreadPoolExecutor executor;
    
        /** 队列存放任务数 **/
        private static int QUEUE_MAX_SIZE = 1000;
    
        /** 线程存活时间 **/
        private static long KEEP_ALIVE_TIME = 1000;
    
        public static ThreadPoolExecutor get(int type) {
            if (executor == null) {
                synchronized (ThreadFactory.class) {
                    if (executor == null) {
                        int cpuNum = Runtime.getRuntime().availableProcessors();
                        int coreSize = cpuNum;
                        if (type == TypeEnum.CPU.getCode()) {
                            coreSize = cpuNum + 1;
                        } else if (type == TypeEnum.IO.getCode()) {
                            coreSize = cpuNum * 10;
                        }
                        int maxSize = coreSize;
                        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(QUEUE_MAX_SIZE);
                        executor = new ThreadPoolExecutor(coreSize, maxSize, KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, queue);
                    }
                }
            }
            return executor;
        }
    
        /**
         * 本机8核16处理器
         */
        public static void main(String[] args) {
            int cpuNum = Runtime.getRuntime().availableProcessors(); // 16
            System.out.println("cpuNum=" + cpuNum);
        }
    }
    
    • 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

    1.5 访问端点

    @RestController
    @RequestMapping("/test")
    public class BizController {
    
        @Resource
        private BService bservice;
        @Resource
        private CService cservice;
    
        @PostMapping("/biz1")
        public boolean biz1(@RequestBody BizParamDTO param) throws Exception {
            bservice.b(param);
            return true;
        }
    
        @PostMapping("/biz2")
        public boolean biz2(@RequestBody BizParamDTO param) throws Exception {
            cservice.c(param);
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2 单次执行

    2.1 同步执行

    postman访问端点biz1,此时选择CPU密集型线程池:

    http://localhost:8080/javafront/test/biz1
    {
        "field": "a"
    }
    
    • 1
    • 2
    • 3
    • 4

    耗时日志如下:

    b1-costTime=1036ms
    
    • 1

    耗时计算公式:

    100ms(a1) + 100ms(a2) + 100ms(a3) + 100ms(a4) + 500ms(a5) = 1000ms
    
    • 1

    2.2 异步执行

    postman访问端点biz2:

    http://localhost:8080/javafront/test/biz2
    {
        "field": "a"
    }
    
    • 1
    • 2
    • 3
    • 4

    耗时日志如下:

    a2-costTime=104ms
    a4-costTime=104ms
    a1-costTime=104ms
    a3-costTime=104ms
    a5-costTime=602ms
    c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    105678000  017%  f1.get()
    000001800  000%  f2.get()
    000048100  000%  f3.get()
    000000400  000%  f4.get()
    498495700  083%  f5.get()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    本次耗时日志稍显复杂,可以把日志分为执行部分和等待部分:

    • 执行部分
      • a1-a4分别执行耗时104ms
      • a5执行耗时602ms
    a2-costTime=104ms
    a4-costTime=104ms
    a1-costTime=104ms
    a3-costTime=104ms
    a5-costTime=602ms
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 等待部分
      • c1总共耗时604ms
      • f1等待时间105ms
      • f2-f4等待时间为纳秒级
      • f5等待时间498ms
    c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    105678000  017%  f1.get()
    000001800  000%  f2.get()
    000048100  000%  f3.get()
    000000400  000%  f4.get()
    498495700  083%  f5.get()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3 本章总结

    • 同步执行耗时为每个节点耗时累加
    • 异步执行耗时为节点中耗时最长节点
    • 单次执行耗时异步优于同步

    3 压力测试

    3.1 压测思路

    • 使用工具JMeter
    • 线程组配置
      • 线程数10、20、30递增
      • Ramp-Up时间0秒
      • 持续时间60s
      • 循环次数永久
    • 关注聚合报告指标
      • 95Line
      • 吞吐量
      • 异常比例
    • 执行方式
      • 同步执行
      • 异步执行,线程池使用CPU密集型
      • 异步执行,线程池使用IO密集型

    3.2 压测分析

    3.2.1 压测结果

    请添加图片描述

    • 同步执行
      • 在不同线程数下耗时总体稳定,均为1000ms左右
    • 异步(IO密集型)
      • 在不同线程数下耗时总体稳定,均为600ms左右
    • 异步(CPU密集型)
      • 随着线程数增多,耗时越来越大,性能表现不如同步

    3.2.2 耗时分析

    现在分析异步在50线程时耗时日志,分析耗时主要发生执行部分,还是发生在等待部分。


    (1) 异步(IO密集型)
    a5-costTime=601ms
    a1-costTime=108ms
    a4-costTime=108ms
    a2-costTime=108ms
    a3-costTime=108ms
    c1-costTime=602ms,costTimeDetail=StopWatch 'waitWatch': running time = 602183001 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    105515000  018%  f1.get()
    000000200  000%  f2.get()
    000777201  000%  f3.get()
    000101800  000%  f4.get()
    495788800  082%  f5.get()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 执行部分
      • a1-a4分别执行耗时约为108ms
      • a5执行耗时601ms
    • 等待部分
      • f1等待时间约为150ms
      • f5等待时间约为495ms
    • 分析小结
      • 执行和等待均符合预期

    (2) 异步(CPU密集型)
    a1-costTime=110ms
    a2-costTime=109ms
    a3-costTime=110ms
    a5-costTime=613ms
    a4-costTime=110ms
    c1-costTime=3080ms,costTimeDetail=StopWatch 'waitWatch': running time = 3080277201 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    2528728000  082%  f1.get()
    016059001   001%  f2.get()
    031992801   001%  f3.get()
    000046199   000%  f4.get()
    503451200   016%  f5.get()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 执行部分

      • a1-a4分别执行耗时约为110ms
      • a5执行耗时613ms
    • 等待部分

      • f1等待时间约为2.5s
      • f5等待时间约为500ms
      • 其它任务等待时间也有所增加
    • 分析小结

      • 耗时主要在等待部分
      • 执行部分耗时符合预期

    3.3 压测总结

    • 如果线程池选择不合适,异步性能不如同步
    • 如果任务耗时长,应该增加配置线程数

    4 文章总结

    本文第一编写了同步和异步代码,并在代码中输出了耗时日志。第二分析单次执行同步和异步的表现,异步优于同步。第三结合不同线程池配置进行压测,如果线程池选择不合适,异步执行性能不如同步,所以要配置合适线程数。


    欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习

  • 相关阅读:
    UrlBasedViewResolver类简介说明
    马斯克热搜体质无疑,称已将大脑上传云端,却遭网友热议!
    C# 自定义事件
    【学习笔记】在 windows 下创建多线程 C++
    【源码系列】情侣游戏小程序系统开发飞行棋扫雷大冒险
    C# 文件/文件夹操作(文本写入,追加,覆盖,清空,文件/文件夹新建,复制,删除,移动)+驱动器+目录+路径+Path类大全
    PyTorch深度学习遥感影像地物分类与目标检测、分割及遥感影像问题深度学习优化实践技术应用
    Flink自定义sink并支持insert overwrite 功能
    使用Python CV2自动识别人脸并融合至新图片
    2022 IDEA (学生邮箱认证)安装使用教程以及基础配置教程
  • 原文地址:https://blog.csdn.net/woshixuye/article/details/126806259