文章目录
阅读本文前可以先阅读我的另一篇博文:Java面试篇(线程池相关专题)
1. 案例引入
在一个电商网站中,当用户查询订单时,一般会包含三部分信息:
- 订单信息
- 包含的商品
- 物流信息
这三部分信息需要通过不同的微服务来获取,我们该如何完成这个业务呢
2. 常规方案
2.1 示例图
我们先来看一下常规的方案,先查询订单信息、再查询商品信息、最后查询物流信息,整个流程中每个部分是串行化执行的
2.2 示例代码
import java.util.ArrayList;
import java.util.List;
public class SingleThreadQuery {
/**
* 模拟从数据库获取订单详情并计算执行时间
*
* @throws InterruptedException 如果在睡眠过程中中断
*/
public static void main(String[] args) throws InterruptedException {
// 记录开始时间
long startTime = System.currentTimeMillis();
// 初始化订单详情列表
List<String> orderDetails = new ArrayList<>();
// 模拟从三个不同的微服务获取数据
orderDetails.add(fetchDataFromDatabase(1));
orderDetails.add(fetchDataFromDatabase(2));
orderDetails.add(fetchDataFromDatabase(3));
// 输出订单详情
System.out.println("SingleThreadQuery orderDetails = " + orderDetails);
// 记录结束时间
long endTime = System.currentTimeMillis();
// 计算并输出耗时
System.out.println("SingleThreadQuery 耗时:" + (endTime - startTime) / 1000 + "秒" + (endTime - startTime) % 1000 + "毫秒");
}
private static String fetchDataFromDatabase(Integer type) throws InterruptedException {
if (type == 1) {
Thread.sleep(500);
return "订单信息";
}
if (type == 2) {
Thread.sleep(800);
return "商品信息";
}
Thread.sleep(500);
return "物流信息";
}
}
2.3 代码执行耗时
3. 使用多线程的方案
采用多线程的方案,需要使用 Future 接口
线程池的 submit 方法执行后会返回一个结果,结果的类型为 Future 接口,调用 Future 接口的 get 方法能够获取到线程执行任务后的返回值
扫描二维码关注公众号,回复: 17431027 查看本文章![]()
值得注意的是,调用 get 方法时会阻塞当前线程,直到成功获取线程执行任务后的返回值
3.1 示例图
我们再来看使用多线程的方案,查询订单信息、查询商品信息、查询物流信息三个操作相当于同时进行,整个流程中每个部分是并发执行的
3.2 示例代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MultipleThreadQuery {
/**
* 本程序通过固定大小的线程池异步获取数据库数据,并计算总耗时
*
* @throws InterruptedException 如果线程被中断
* @throws ExecutionException 如果Future.get()方法执行异常
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 记录开始时间
long startTime = System.currentTimeMillis();
// 初始化存储订单详情的列表
List<String> orderDetails = Collections.synchronizedList(new ArrayList<>());
// 初始化存储Future对象的列表,用于获取异步计算结果
List<Future<String>> futureList = new ArrayList<>();
// 创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交三个任务到线程池异步执行
futureList.add(executorService.submit(() -> fetchDataFromDatabase(1)));
futureList.add(executorService.submit(() -> fetchDataFromDatabase(2)));
futureList.add(executorService.submit(() -> fetchDataFromDatabase(3)));
// 遍历Future列表,获取异步计算结果,并添加到订单详情列表中
for (Future<String> future : futureList) {
orderDetails.add(future.get());
}
// 打印订单详情
System.out.println("MultipleThreadQuery orderDetails = " + orderDetails);
// 记录结束时间
long endTime = System.currentTimeMillis();
// 计算并打印耗时
System.out.println("MultipleThreadQuery 耗时:" + (endTime - startTime) / 1000 + "秒" + (endTime - startTime) % 1000 + "毫秒");
// 关闭线程池,不再接受新任务,等待所有已提交的任务完成
executorService.shutdown();
}
private static String fetchDataFromDatabase(Integer type) throws InterruptedException {
if (type == 1) {
Thread.sleep(500);
return "订单信息";
}
if (type == 2) {
Thread.sleep(800);
return "商品信息";
}
Thread.sleep(500);
return "物流信息";
}
}
3.3 代码执行耗时
4. 常规方案和使用多线程方案的对比
可以看到,使用多线程的方案的耗时远远小于常规方案
5. 注意事项
- 在汇总结果时,要确保用于汇总结果的集合是线程安全的,避免出现与线程安全相关的问题
- 一般使用多线程提高接口的响应速度只用于纯查询的操作,如果是涉及到修改数据的操作,不建议使用多线程,因为修改数据操作一般会涉及到事务,在多线程的情况下,事务管理将变得不可控
- 在项目中使用多线程时,一般是配合线程池使用,如果线程池中的线程都被其它耗时较长的业务占用了,将会出现一些意想不到的问题