启用多线程调用接口

本文讲述了在Java中如何使用线程池、Callable和FutureTask来优化接口调用,解决单线程顺序调用导致的响应时间过长问题。通过创建线程池,使用Callable封装带有返回值的业务方法,再利用FutureTask控制返回顺序,确保接口调用的顺序性,从而提高程序效率。

最近在做一个定时任务接口,接口的流程很简单,就是调用几个接口,然后将这这些接口的返回json字符串解析,存入后台数据库,编码很顺利,很快完成了。

随后负责人很快就发现问题,由于是单线程顺序调用,这些接口调用时间是线性叠加的,(假如前一个接口耗时10秒,在它结束前第二个接口一直处于阻塞中,这前一个接口调用所花费的时间就完全浪费了)我的这个模块响应时间已经超过了一分钟,于是提出了启用线程池的优化方案。

因为时间精力有限,暂时不研究线程池 阻塞队列这些,这里只提供一个简单易懂的解决方案

我很快选定了缓冲池作为线程池,具体创建过程代码如下

ExecutorService executorService = Executors.newCachedThreadPool();

第一次编码时,我用强大的百度查到了线程创建方法,也是我之前背诵的滚瓜烂熟的两种方式

extends Thread 或者 implements Runnable

当时我心想,这下我背的东西可是用得着了,于是我毫不犹豫,当场建了几个Thread类,用run方法包裹了自己的方法,一共5个接口,我建了5个Thread,分别包裹了这5个方法,迅速完成了5个线程,再依次调用线程池的submit方法依次传入这5个线程对象。

Thread thread1 = new Thread(new Runnable() {
  @Override
  public void run() {
    //封装的调用接口的方法,
    getCompareInfo(params);
  }
});

Thread thread1 ...................................

executorService.submit(thread1);
executorService.submit(thread2);
executorService.submit(thread3);
executorService.submit(thread4);
executorService.submit(thread5);

代码很少,很快写完,让我顿时产生多线程不过如此的错觉,也就是创建一个线程然后用线程池去调度的事情,写完submit方法我觉得问题已经解决了,但是,当我想取回线程中调用接口的返回值然后解析的时候我意识到了一个尴尬的问题,Thread中的public void run()方法时没有返回值,这意味着我无法获取接口的返回。于是我思考了下,如果是启用线程去做写DB,或者存东西之类的操作我想用Thread是可行的,但是需要返回结果的时候就不行了

又是通过强大的专业广告搜索引擎,我百度倒了Callable这个类,并找到了应用的示例。

ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService<Map<String,String>> cs = new ExecutorCompletionService<>( executorService);
Callable compareCallable = new Callable() {
  @Override
  public Map<String, String> call() {
    return getCompareInfo(params);
  }
};
cs.submit(compareCallable);
cs.submit(*);
cs.submit(*);
cs.submit(*);
cs.submit(*);
Map<String,String> res1 = cs.take().get();
Map<String,String> res2 = cs.take().get();
Map<String,String> res3 = cs.take().get();
Map<String,String> res4 = cs.take().get();
Map<String,String> res5 = cs.take().get();

我用Callable取代了Thread去执行接口调用方法,这里调度的对象换成了ExecutorCompletionService声明返回泛型是个map,最后按照调用顺序来获取接口返回

但随后的测试中我发现一个问题,我调用接口的顺序是

接口1

接口2

接口3

接口4

接口5

但是返回的顺序是1 2 5 3 4

因为接口响应有快有慢,比较快的接口先调用结束,就先返回了,顺序直接受到接口响应时间的影响,我还是无法拿到返回值,因为我不知道接口返回的顺寻,它是变动的

最后问了下业务熟悉的大佬,找到了FutureTask这个类,保留先前写好的Callable,要用到

//构建futureTask任务,控制返回顺序
FutureTask<Map<String,String>> futureTask1 = new FutureTask<Map<String,String>>(compareCallable);
FutureTask<Map<String,String>> futureTask2 = new FutureTask<Map<String,String>>(getDeptInfoCallable);
FutureTask<Map<String,String>> futureTask3 = new FutureTask<Map<String,String>>(getPaymentDetailInfoCallable);
FutureTask<Map<String,String>> futureTask4 = new FutureTask<Map<String,String>>(getAllowanceDetailCallable);

直接把之前的callable对象作为参数传递到FutureTask的构造方法中,然后换成

ExecutorService executorService = Executors.newCachedThreadPool();

executorService的submit方法,调用

Map<String,String> res = futureTask1.get();

Map<String,String> res = futureTask2.get();

Map<String,String> res = futureTask3.get();

Map<String,String> res = futureTask4.get();

Map<String,String> res = futureTask5.get();

这样就能按照顺序返回接口的响应了,在方法内我们可以用try catch包裹业务代码块处理异常,也可以用线程执行的返回状态判断接口调用是否正常,来设定重试机制,FutureTask的原理是阻塞队列,在任务结束之前会阻塞,所以取到的返回是有顺序的。

若是你的接口中存在多个调用三方接口的操作,或者多个大批量数据处理的工作,且它们之前是没有关联先后顺序的,你也可以像这样创建线程并启用线程池并行执行这些操作,我的接口在高早前响应时间是70秒,改造后10秒左右就可以结束,虽说不是很快,但相比之前已经强多了。

总结就是,使用Callable对象封装具体业务代码,然后依次塞进FutureTask,使用线程池调度,然后依次调用get方法取出返回值即可,这样就可以避免单线程代码中,后续业务因为前面的业务耗时长而造成的block(阻塞)带来的长响应时间。

转载请注明出处

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值