启用多线程调用接口

最近在做一个定时任务接口,接口的流程很简单,就是调用几个接口,然后将这这些接口的返回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(阻塞)带来的长响应时间。

转载请注明出处

 

  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt对线程提供了支持,基本形式有独立于平台的线程类、线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法。 这个文档是提供给那些对多线程编程有丰富的知识和经验的听众的。推荐阅读: Threads Primer: A Guide to Multithreaded Programming Thread Time: The Multithreaded Programming Guide Pthreads Programming: A POSIX Standard for Better Multiprocessing (O'Reilly Nutshell) Win32 Multithreaded Programming 警告:所有的GUI类(比如,QWidget和它的子类),操作系统核心类(比如,QProcess)和网络类都不是线程安全的。 QRegExp使用一个静态缓存并且也不是线程安全的,即使通过使用QMutex来保护的QRegExp对象。 启用线程支持 在Windows上安装Qt时,在一些编译器上线程支持是一个选项。 在Mac OS X和Unix上,线程支持可以当你在运行configure脚本时添加-thread选项就可以生效了。在Unix平台上,多线程程序必须用特殊的方式连接,比如使用特殊的libc,安装程序将会创建另外一个库libqt-mt并且因此线程程序必须和这个库进行连接(使用-lqt-mt)而不是标准的Qt库。 在两个平台上,你都应该定义宏QT_THREAD_SUPPORT来编译(比如,编译时使用-DQT_THREAD_SUPPORT)。在Windows上,这个通常可以在qconfig.h写一个条目来解决。 线程类 最重要的类是QThread,也就是说要开始一个新的线程,就是开始执行你重新实现的QThread::run()。这和Java的线程类很相似。 为了写线程程序,在两个线程同时希望访问同一个数据时,对数据进行保护是很必要的。因此这里也有一个QMutex类,一
下面是一个使用 Spring Boot 中的 @Async 注解来实现多线程调用 RestTemplate 的示例代码: 首先,在启动类上添加 @EnableAsync 注解,启用异步处理: ```java @SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 接着,在配置类中创建 RestTemplate 的实例,并将其作为 Bean 注入到容器中: ```java @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } ``` 然后,在 Service 类中定义一个异步方法,使用 @Async 注解标记该方法为异步处理: ```java @Service public class UserService { @Autowired private RestTemplate restTemplate; @Async public CompletableFuture<User> getUserById(int id) { String url = "https://example.com/api/user/{id}"; User user = restTemplate.getForObject(url, User.class, id); return CompletableFuture.completedFuture(user); } } ``` 在该方法中,我们使用 RestTemplate 发送了一个 GET 请求,并将响应转换成了 User 对象。然后,我们使用 CompletableFuture.completedFuture 方法将 User 对象包装成 CompletableFuture 对象,并返回。 最后,在 Controller 类中调用 UserService 的异步方法: ```java @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public CompletableFuture<User> getUserById(@PathVariable int id) { return userService.getUserById(id); } } ``` 在该方法中,我们调用了 UserService 的 getUserById 方法,并返回了一个 CompletableFuture 对象。 需要注意的是,在使用 @Async 注解时,需要注意线程池的配置和异常处理。可以在配置类中通过配置 ThreadPoolTaskExecutor 来自定义线程池的大小和其他属性。在方法中,需要使用 try-catch 块来捕获异常,并进行适当的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值