接口耗时太久怎么办?适当用点Future做异步~

简介: 接口耗时太久怎么办?适当用点Future做异步~

在JS中,各种回调函数漫天飞,写的代码基本都是异步的,需要靠某个event去trigger。比如点击事件,就是最常见的。但是Java里面做异步就得绕好几弯,还不一定行,需要各种判断和校验,生怕出错。



异步执行的好处是显而易见的,比如某个接口需要查好几张表,调很多方法,其中有几个方法是非常耗时的。但是很多代码的做法还是同步的,传统的顺序执行。优点是可读性强,不容易出错。


假如A方法耗时特别长的话,那么整合接口的返回就会很慢。


比如,现在有一个服务,非常耗时。

class AccountService {
//耗时6秒的任务
    List<String> queryAccountInfo(String accountNo) throws InterruptedException {
        ArrayList<String> accountList = new ArrayList<String>();
        accountList.add("A账户--" + accountNo);
        accountList.add("账户状态:正常");
        accountList.add("账户额度:8500");
        Thread.sleep(3000);
        return accountList;
    }
}


对于这种耗时任务,当然是希望他可以单独去跑,他在跑的时候,别影响我去做别的事情。


最简单的思路就是用一个线程,可是这样就有一个问题,啥时候任务完成呢?所以,就挺烦的。还好JDK给我们提供了一个很好的类--FutureTask。


从字面意义上看,他是一个未来任务,构造方法如下:

    /**
      * Creates a {@code FutureTask} that will, upon running, execute the
      * given {@code Callable}.
      *
      * @param  callable the callable task
      * @throws NullPointerException if the callable is null
      */
            public FutureTask(Callable<V> callable){
                if (callable ==null)
                    throw new NullPointerException();
                this.callable =callable;
                this.state=NEW;       // ensure visibility of callable
            }


他接收一个callable对象,还有一个构造器重载:

   /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }


接收Runnable对象。

嗯,按照一贯的套路,如果是自己设计,肯定会弄一个Runnable属性,可是FutureTask偏偏没有这么做,而是用了适配器模式:


this.callable = Executors.callable(runnable, result);


就是强行把Runnabale对象转换为callable对象。


ca6d4c49b04347cd86f40d12122bf002.png


因为Callable有返回值但是Runnable没有,所以就给一个假的result。换句话说,这个result是你自己传进去的,等任务执行完毕后,再还给你。


下面是一个测试Demo,总体来看,确实可以优化接口的返回时间,代价是需要增加好几个类。

class WrongArgumentException extends Exception {
        public WrongArgumentException(String message){
                super(message);
        }
}
class AccountService {
        //耗时6秒的任务
        List<String> queryAccountInfo(String accountNo)throws InterruptedException{
        ArrayList<String> accountList=new ArrayList<String>();
        accountList.add("A账户--" +accountNo);
        accountList.add("账户状态:正常");
        accountList.add("账户额度:8500");
        Thread.sleep(3000);
        return accountList;
        }
}
class AccountQueryService extends AccountService implements Callable{
private String accountNo;
public AccountQueryService(String accountNo){
        this.accountNo=accountNo;
}
@Override
public Object call()throws Exception{
        if(accountNo==null){
            throw new WrongArgumentException("参数传递错误:accountNo为空!");
        }
        return queryAccountInfo(accountNo);
}
        }
public class TestFutureTask {
    public static void main(String[]args)throws InterruptedException,ExecutionException{
            long start=System.currentTimeMillis();
            //创建线程池
            ExecutorService newSingleThreadExecutor =Executors.newSingleThreadExecutor();
            Map<String, Object> result=new java.util.HashMap<String, Object>();
            //执行耗时操作:查询账户信息 ,直接返回结果,不过现在为空
            Future<List> task=newSingleThreadExecutor.submit(new AccountQueryService("5201314"));
            //不影响主线程,异步线程自己在执行
            Thread.sleep(1000);
            result.put("cardInfo","主卡信息:奥特曼星球联名卡");
             //等待异步线程是否结束
            int timeout=5000; //接口超时时间5
            while(!task.isDone()){
                if(System.currentTimeMillis()-start>timeout){
                    System.out.println("任务超时,提前结束!");
                    result.put("code","-1");
                    result.put("msg","task time out!");
                    System.out.println(result);
                    return;
                }
                Thread.sleep(100);
             //System.out.println("账户查询任务尚未结束,请耐心等到1秒...");
            }
            //获取异步任务的结果
            result.put("accountInfo",task.get());
            result.put("code","00000");
            newSingleThreadExecutor.shutdown(); //关闭线程池
            System.out.println(result);
            System.out.println("任务总耗时:" +(System.currentTimeMillis()-start)/1000 +"秒");
        }
}


运行结果:


{accountInfo=[A账户--5201314, 账户状态:正常, 账户额度:8500], cardInfo=主卡信息:奥特曼星球联名卡, code=00000}

任务总耗时:3秒


虽然代码还很粗糙,不过效果还是有的。对于那种耗时特别久,而且是多任务的情况,胆子够肥的话可以试试。


相关文章
|
2月前
|
缓存 Java
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
这篇文章详细介绍了Java中线程的四种初始化方式,包括继承Thread类、实现Runnable接口、实现Callable接口与FutureTask结合使用,以及使用线程池。同时,还深入探讨了线程池的七大参数及其作用,解释了线程池的运行流程,并列举了四种常见的线程池类型。最后,阐述了在开发中使用线程池的原因,如降低资源消耗、提高响应速度和增强线程的可管理性。
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
|
2月前
|
Java 数据库
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】
这篇文章通过一个电商商品详情页的实战案例,展示了如何使用`CompletableFuture`进行异步编排,以解决在不同数据库表中查询商品信息的问题,并提供了详细的代码实现和遇到问题(如图片未显示)的解决方案。
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】
|
2月前
|
负载均衡 算法 网络协议
异步任务处理系统问题之Level 1的异步任务处理系统的问题如何解决
异步任务处理系统问题之Level 1的异步任务处理系统的问题如何解决
|
5月前
|
Java
使用线程池异步执行
使用线程池异步执行
32 0
|
11月前
同步和异步[多线程的异步执行操作]
同步和异步[多线程的异步执行操作]
48 0
同步调用和异步调用
同步调用和异步调用
|
Java Maven
java多线程提交,如何按照时间顺序获取线程结果,看完你就懂了 | Java工具类
java多线程提交,如何按照时间顺序获取线程结果,看完你就懂了 | Java工具类
java多线程提交,如何按照时间顺序获取线程结果,看完你就懂了 | Java工具类
JUC(二)JAVA线程池开启,等待全部执行完毕,配合计数器使用,List并发异常解决
JUC(二)JAVA线程池开启,等待全部执行完毕,配合计数器使用,List并发异常解决
JUC(二)JAVA线程池开启,等待全部执行完毕,配合计数器使用,List并发异常解决
|
Go
Go并发之同步异步、异步回调
众所周知,Go语言最强大的地方在于它支持的高并发特性。下面我们先来了解一下Go并发的一些理论基础:同步异步、异步回调。也顺带在此介绍一下进程、线程、协程的区别。
658 0
Go并发之同步异步、异步回调
|
前端开发 小程序
处理小程序网络请求异步执行的问题
处理小程序网络请求异步执行的问题
202 0