实践:使用了CompletableFuture之后,程序性能提升了三倍

简介: 实践:使用了CompletableFuture之后,程序性能提升了三倍

CompletableFuture


相比于jdk5所提出的future概念,future在执行的时候支持异步处理,但是在回调的过程中依旧是难免会遇到需要等待的情况。


在jdk8里面,出现了CompletableFuture的新概念,支持对于异步处理完成任务之后自行处理数据。当发生异常的时候也能按照自定义的逻辑来处理。


如何通过使用CompletableFuture提升查询的性能呢?


下边我举个例子来演示:


首先我们定义一个UserInfo对象:


/**
 * @author idea
 * @data 2020/2/22
 */
public class UserInfo {
    private Integer id;
    private String name;
    private Integer jobId;
    private String jobDes;
    private Integer carId;
    private String carDes;
    private Integer homeId;
    private String homeDes;
    public Integer getId() {
        return id;
    }
    public UserInfo setId(Integer id) {
        this.id = id;
        return this;
    }
    public String getName() {
        return name;
    }
    public UserInfo setName(String name) {
        this.name = name;
        return this;
    }
    public Integer getJobId() {
        return jobId;
    }
    public UserInfo setJobId(Integer jobId) {
        this.jobId = jobId;
        return this;
    }
    public String getJobDes() {
        return jobDes;
    }
    public UserInfo setJobDes(String jobDes) {
        this.jobDes = jobDes;
        return this;
    }
    public Integer getCarId() {
        return carId;
    }
    public UserInfo setCarId(Integer carId) {
        this.carId = carId;
        return this;
    }
    public String getCarDes() {
        return carDes;
    }
    public UserInfo setCarDes(String carDes) {
        this.carDes = carDes;
        return this;
    }
    public Integer getHomeId() {
        return homeId;
    }
    public UserInfo setHomeId(Integer homeId) {
        this.homeId = homeId;
        return this;
    }
    public String getHomeDes() {
        return homeDes;
    }
    public UserInfo setHomeDes(String homeDes) {
        this.homeDes = homeDes;
        return this;
    }
}


这个对象里面的homeid,jobid,carid都是用于匹配对应的住房信息描述,职业信息描述,购车信息描述。


对于将id转换为描述信息的方式需要通过额外的sql查询,这里做了个简单的工具类来进行模拟:


import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
 * @author idea
 * @data 2020/2/22
 */
public class QueryUtils {
    public String queryCar(Integer carId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "car_desc";
    }
    public String queryJob(Integer jobId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "job_desc";
    }
    public String queryHome(Integer homeId){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "home_desc";
    }
}


这个工具类的功能看起来会比较通俗易懂,在常规的逻辑里面,我们做批量对象的转换大多数都是基于List遍历,然后在循环里面批量查询,这样的方式并非说不行,而是显得比较过于“暴力”。


假设每次查询需要消耗1s,那么遍历的一个size为n的集合查询消耗的时间就是n * 3s。


下边来介绍一种更为方便的技巧:CompletableFuture


定义一个QuerySupplier 实现Supplier接口,根据注入的类型进行转译查询:


import java.util.function.Supplier;
public class QuerySuppiler implements Supplier<String> {
        private Integer id;
        private String type;
        private QueryUtils queryUtils;
        public QuerySuppiler(Integer id, String type,QueryUtils queryUtils) {
            this.id = id;
            this.type = type;
            this.queryUtils=queryUtils;
        }
        @Override
        public String get() {
            if("home".equals(type)){
                return queryUtils.queryHome(id);
            }else if ("job".equals(type)){
                return queryUtils.queryJob(id);
            }else if ("car".equals(type)){
                return queryUtils.queryCar(id);
            }
            return null;
        }
    }


由于对应的carid,homeid,jobid都需要到指定的k,v配置表里面通过核心查询包装器来进行转译,因此通常的做法就是在for循环里面一个个地进行遍历解析,这样的做法也比较易于理解。


QuerySuppiler 是我写的一个用于做对象解析的服务,代码如下所示:


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
 * @author idea
 * @data 2020/2/22
 */
public class QueryUserService {
    private Supplier<QueryUtils> queryUtilsSupplier = QueryUtils::new;
    public UserInfo converUserInfo(UserInfo userInfo) {
        QuerySuppiler querySuppiler1 = new QuerySuppiler(userInfo.getCarId(), "car", queryUtilsSupplier.get());
        CompletableFuture<String> getCarDesc = CompletableFuture.supplyAsync(querySuppiler1);
        getCarDesc.thenAccept(new Consumer<String>() {  --1
            @Override
            public void accept(String carDesc) {
                userInfo.setCarDes(carDesc);
            }
        });
        QuerySuppiler querySuppiler2 = new QuerySuppiler(userInfo.getHomeId(), "home", queryUtilsSupplier.get());
        CompletableFuture<String> getHomeDesc = CompletableFuture.supplyAsync(querySuppiler2);
        getHomeDesc.thenAccept(new Consumer<String>() {  --2
            @Override
            public void accept(String homeDesc) {
                userInfo.setHomeDes(homeDesc);
            }
        });
        QuerySuppiler querySuppiler3 = new QuerySuppiler(userInfo.getJobId(), "job", queryUtilsSupplier.get());
        CompletableFuture<String> getJobDesc = CompletableFuture.supplyAsync(querySuppiler3);
        getJobDesc.thenAccept(new Consumer<String>() {  --3
            @Override
            public void accept(String jobDesc) {
                userInfo.setJobDes(jobDesc);
            }
        });
        CompletableFuture<Void> getUserInfo = CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);
        getUserInfo.thenAccept(new Consumer<Void>() {
            @Override
            public void accept(Void result) {
                System.out.println("全部完成查询" );
            }
        });
        getUserInfo.join();  --4
        return userInfo;
    }
    public static void main(String[] args) {
        long begin= System.currentTimeMillis();
        //多线程环境需要注意线程安全问题
        List<UserInfo> userInfoList=Collections.synchronizedList(new ArrayList<>());
        for(int i=0;i<=20;i++){
            UserInfo userInfo=new UserInfo();
            userInfo.setId(i);
            userInfo.setName("username"+i);
            userInfo.setCarId(i);
            userInfo.setJobId(i);
            userInfo.setHomeId(i);
            userInfoList.add(userInfo);
        }
        //stream 查询一个用户花费3s  并行计算后一个用户1秒左右 查询21个用户花费21秒
        //parallelStream 速度更慢
        userInfoList.stream()
                .map(userInfo->{
                    QueryUserService queryUserService=new QueryUserService();
                    userInfo =queryUserService.converUserInfo(userInfo);
                    return userInfo;
                }).collect(Collectors.toList());
        System.out.println("=============");
        long end=System.currentTimeMillis();
        System.out.println(end-begin);
    }
}


看看这段代码的—1,—2,—3部分,三个执行点的位置在使用了thenAccept组装数据之后,还是可以避开串行化获取数据的情况。只有在—4的位置才会发生堵塞。这样对于性能的提升效果更佳。


这里进行模拟测试,采用原始暴力手段查询所消耗的时间是20 * 3 =60秒,但是这里使用了CompletableFuture之后,查询的时间就会缩短为了21秒。



结果:


全部完成查询
=============
21223


这是一种使用了空间换时间的思路,或许你会说,异步查询如果使用FutureTask是不是也

可以呢。嗯嗯,是的,但是使用future有个问题,就是在于返回获取异步结果的时候需要有等待状态,这个等待的状态是需要消耗时间进行堵塞的。


这里我也做了关于使用普通FutureTask来执行查询优化的结果:


/**
     * 使用 FutureTask 来优化查询
     *
     * @param userInfo
     * @return
     */
    public  UserInfo converUserInfoV2(UserInfo userInfo) {
        Callable<String> homeCallable=new Callable() {
            @Override
            public Object call() throws Exception {
                return queryUtilsSupplier.get().queryHome(userInfo.getHomeId());
            }
        };
        FutureTask<String> getHomeDesc=new FutureTask<>(homeCallable);
        new Thread(getHomeDesc).start();
        futureMap.put("homeCallable",getHomeDesc);
        Callable<String> carCallable=new Callable() {
            @Override
            public Object call() throws Exception {
                return queryUtilsSupplier.get().queryCar(userInfo.getCarId());
            }
        };
        FutureTask<String> getCarDesc=new FutureTask(carCallable);
        new Thread(getCarDesc).start();
        futureMap.put("carCallable",getCarDesc);
        Callable<String> jobCallable=new Callable() {
            @Override
            public Object call() throws Exception {
                return queryUtilsSupplier.get().queryCar(userInfo.getJobId());
            }
        };
        FutureTask<String> getJobDesc=new FutureTask<>(jobCallable);
        new Thread(getJobDesc).start();
        futureMap.put("jobCallable",getJobDesc);
        try {
            userInfo.setHomeDes((String) futureMap.get("homeCallable").get());
            userInfo.setCarDes((String)futureMap.get("carCallable").get());
            userInfo.setJobDes((String)futureMap.get("jobCallable").get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("该对象完成查询" );
        return userInfo;
    }


经过测试,使用 futuretask 进行优化的查询结果只有47s左右,远远不及CompletableFuture的性能高效.这是因为使用了futuretask的get方法依然是存在堵塞的情况。


关键部分看这段内容:


userInfo.setHomeDes((String) futureMap.get("homeCallable").get());  --1
            userInfo.setCarDes((String)futureMap.get("carCallable").get());  --2
            userInfo.setJobDes((String)futureMap.get("jobCallable").get());  --3


—1代码在执行的时候遇到了堵塞,然后—2和—3的get也需要进行等待,因此使用常规的futuretask进行优化,这里难免还是会有堵塞的情况。


END

相关文章
|
23天前
|
数据采集 Cloud Native Java
10 倍性能提升, GraalVM 应用可观测实践
本文介绍了 GraalVM 静态编译技术在云原生环境下的应用:ARMS 发布了支持 GraalVM 应用的 Java Agent 探针,可为 GraalVM 应用提供开箱即用的可观测能力。同时,文章还提供了使用 ARMS 对 GraalVM 应用进行可观测的详细步骤。
|
4天前
|
SQL 缓存 监控
接口性能倍增记:一次成功的优化实践
在软件开发过程中,接口性能优化是提升用户体验和系统稳定性的关键环节。本文将分享一次接口优化的成功案例,从问题发现到解决方案实施,详细介绍我们的优化过程和成果。
13 0
|
4月前
|
分布式计算 并行计算 Java
Python并发风暴来袭!IO密集型与CPU密集型任务并发策略大比拼,你站哪队?
【7月更文挑战第17天】Python并发处理IO密集型(如网络请求)与CPU密集型(如数学计算)任务。IO密集型适合多线程和异步IO,如`ThreadPoolExecutor`进行网页下载;CPU密集型推荐多进程,如`multiprocessing`模块进行并行计算。选择取决于任务类型,理解任务特性是关键,以实现最佳效率。
74 4
|
6月前
|
并行计算 安全 Java
并行编程确实是一种强大的技术,能够显著提升计算效率和性能
【5月更文挑战第16天】并行编程能提升效率,但面临任务分解、数据同步、资源管理等挑战。要编写正确且高效的并行程序,需注意任务粒度控制,确保数据一致性,合理分配资源,选择合适的编程模型和框架,使用专用工具进行测试调试,以及进行性能分析和优化。实践经验与持续学习是提升并行编程技能的关键。
83 0
|
并行计算
R语言多线程使用方法,充分利用计算资源实现高效计算,缩短等待时间
R语言多线程使用方法,充分利用计算资源实现高效计算,缩短等待时间
|
存储 缓存 监控
浅谈系统性能提升的经验和方法
资金核对的数据组装-执行-应急链路,有着千万级TPS并发量,同时由于资金业务特性,对系统可用性和准确性要求非常高;日常开发过程中会遇到各种各样的高可用问题,也在不断地尝试做一些系统设计以及性能优化,在此期间总结了部分性能优化的经验和方法,跟大家一起分享和交流,后续遇到一些新的问题也会持续总结和补充。
39474 17
浅谈系统性能提升的经验和方法
|
SQL NoSQL 安全
只改了五行代码接口吞吐量提升了10多倍
首先,提升日志打印级别到DEBUG。emm... 提升不大,好像增加了10左右。 然后,拆线程 @Async 注解使用线程池,控制代码线程池数量(之前存在3个线程池,统一配置的核心线程数为100)结合业务,服务总核心线程数控制在50以内,同步增加阻塞最大大小。结果还可以,提升了50,接近200了。
173 0
|
存储 缓存 Java
《重学Java高并发》disruptor是如何做到百万级吞吐?
《重学Java高并发》disruptor是如何做到百万级吞吐?
《重学Java高并发》disruptor是如何做到百万级吞吐?
|
JavaScript Cloud Native Oracle
Java 大杀器来了!性能提升一个数量级
自 1996 年诞生以来,Java 语言长期在最受欢迎的编程语言排行榜中占据领先地位。除了语言本身的优秀特性之外,Java 语言持续演进、不断发展也是它能够保持长盛不衰的重要原因。Java 语言的功能和性能都在不断地发展和提高,但是 冷启动开销较大 的问题长期存在,难以从根本上解决。 Java 语言也因此在 Serverless 场景下无法与 Node.js、Go 等快速启动的语言竞争,落于下风。在这种背景下,作为能够从根本上解决冷启动问题的 Java 静态编译技术 有了用武之地,开始在业界崭露头角,为 Java 语言注入了新的竞争力。
Java 大杀器来了!性能提升一个数量级
|
存储 缓存 算法
系统性能百倍提升典型案例分析:高性能队列Disruptor
Disruptor 是一款高性能的有界内存队列,目前应用非常广泛,Log4j2、SpringMessaging、HBase、Storm 都用到了 Disruptor,那 Disruptor 的性能为什么这么高呢?
系统性能百倍提升典型案例分析:高性能队列Disruptor
下一篇
无影云桌面