假如我们有一个记录流水号的列表List<String> lshList = new ArrayList<>(); 此流水号时某一个map中的key,map中的value则是流水号对应的业务数据, 我们想将列表分为三份,通过异步任务分别处理他们,并将处理结果进行合并。
CompletableFuture的应用
1.分割流水号列表。
//将此列表分成三份通过多线程方式去推数据
List<List<String>> listsThree = splitThree(lshList);
List<String> first = listsThree.get(0);
List<String> second = listsThree.get(1);
List<String> three = listsThree.get(2);
/**
* 将集合拆分为三份
* @param list
* @return
*/
public List<List<String>> splitThree(List<String> list){
int size = list.size();
int firstPartSize = size / 3;
int secondPartSize = (size % 3 == 0) ? firstPartSize : firstPartSize + 1;
int thirdPartSize = size - firstPartSize - secondPartSize;
return IntStream.range(0, 3)
.mapToObj(i -> list.subList(
i == 0 ? 0 : (i == 1 ? firstPartSize : firstPartSize + secondPartSize),
i == 0 ? firstPartSize : (i == 1 ? firstPartSize + secondPartSize : size)
))
.collect(Collectors.toList());
}
2.将其三份业务数据根据流水号分别放在不同的map中。
Map<String,ReqMasters> reqMastersFirstMap = new HashMap<>();
Map<String,ReqMasters> reqMastersSecondHalfMap = new HashMap<>();
Map<String,ReqMasters> reqMastersThreeHalfMap = new HashMap<>();
// 分三份
first.forEach(obj->{
reqMastersFirstMap.put(obj,reqMastersMap.get(obj));
});
second.forEach(obj->{
reqMastersSecondHalfMap.put(obj,reqMastersMap.get(obj));
});
three.forEach(obj->{
reqMastersThreeHalfMap.put(obj,reqMastersMap.get(obj));
});
3.创建三个异步任务,对这三份业务数据分别进行处理。
// 创建第一个异步任务
CompletableFuture<List<VerificationDto>> future1 = CompletableFuture.supplyAsync(() -> {
//任务1
return newOrder(reqMastersFirstMap,"Thread1");
});
// 创建第二个异步任务
CompletableFuture<List<VerificationDto>> future2 = CompletableFuture.supplyAsync(() -> {
//任务2
return newOrder(reqMastersSecondHalfMap,"Thread2");
});
// 创建第三个异步任务
CompletableFuture<List<VerificationDto>> future3 = CompletableFuture.supplyAsync(() -> {
//任务2
return newOrder(reqMastersThreeHalfMap,"Thread3");
});
newOrder接口如下,实现类省略:
public List<VerificationDto> newOrder(Map<String, ReqMasters> reqMastersMap,String threadName);
4.等待其任务执行完毕后,将任务结果进行合并。
// 使用CompletableFuture.allOf等待所有任务完成
// 等待所有任务完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);
// 等待所有任务完成,并获取结果 ,join方法会阻塞直到所有任务完成
allFutures.join();
// 合并任务结果
List<List<VerificationDto>> lists = Arrays.asList(future1.join(), future2.join(), future3.join());
ExecutorService的应用
假如我们有一个需要回传报告的列表,此回传报告任务无返回信息,异常信息都在任务内自动记表。
List<LisOrder> finishLisOrderReport = new ArrayList<>();
LisOrder对象中有个属性threadNum提前记录它应该被分配到的线程组,现在我们根据threadNum对list进行划分。
//根据threadnum对list进行划分
Map<Integer, List<LisOrder>> groupedOrders = finishLisOrderReport.stream()
.collect(Collectors.groupingBy(LisOrder::getThreadNum));
划分后我们根据这个map的大小(注意不是list的大小)创建一个固定的线程池。
// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(groupedOrders.size());
接下来我们把划分好的任务,提交给线程池,通过多线程的方式去执行:
// groupedOrders 现在是一个Map,它的键是threadNum,值是对应的LisOrder列表
for (Map.Entry<Integer, List<LisOrder>> entry : groupedOrders.entrySet()) {
log.info("Thread " + entry.getKey());
List<LisOrder> subList = entry.getValue();
executorService.submit(() -> {
try {
for (LisOrder order : subList) {
getReportFromLis(order);
log.info("推送报告数据:"+order.getLisTid());
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
executorService.shutdown(); // 关闭线程池
getReportFromLis接口如下,具体实现类省略。
public Result<?> getReportFromLis(LisOrder lisOrder);
不过如上的ExecutorService这样进行多任务划分有点脱裤子放屁并不能足以发挥多线程的威力,每个任务里的for循环还是同步执行的(好处就是将任务划分开了),如果任务列表很大其实还是只是作为几个子任务进行异步执行,我们也可以完全给改造成多线程的方式(不过这个也有缺点,没有提前控制可能会造成线程安全问题),Java中的多任务和多线程其实都是实现并发执行的方法,这里的例子的区别就是我提前把任务划分出来交给多线程去执行(能提前分组更好控制并发),我们还能这样写,不划分任务一股脑的都提交给线程池:
public class updateLisDataThread implements Callable<Boolean> {
private LisOrder order;
public updateLisDataThread(LisOrder order) {
this.order = order;
}
@Override
public Boolean call() throws Exception {
// 这里是保存LisOrder到数据库的代码
// 假设这个方法会返回true如果保存成功,false如果失败
// 注意:这里的代码需要根据你的实际数据库操作来编写
boolean success = updateLisData(order);
return success;
}
private boolean updateLisData(LisOrder order) {
// 示例:仅打印到控制台,实际中应替换为数据库保存操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("保存数据: " + order.getLisTid());
System.out.println("----------");
// 假设总是成功
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<LisOrder> orders = new ArrayList<>();
// 假设orders已经被填充了数据
for (int i = 0; i < 30; i++) {
LisOrder lisOrder = new LisOrder();
lisOrder.setLisTid("000"+i);
orders.add(lisOrder);
}
ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
List<Future<Boolean>> futures = new ArrayList<>();
for (LisOrder order : orders) {
Future<Boolean> future = executorService.submit(new updateLisDataThread(order));
futures.add(future);
}
// 等待所有任务完成并处理结果(如果需要的话)
for (Future<Boolean> future : futures) {
Boolean result = future.get(); // 这会阻塞直到任务完成
if (!result) {
// 处理保存失败的情况
System.err.println("保存数据失败.");
}
}
executorService.shutdown(); // 关闭线程池
while (!executorService.isTerminated()) {
// 等待线程池中的所有任务都完成
}
System.err.println("执行完毕.");
}
}
如果线程池大小为5,同时间只有五个线程在运行吗?
如果线程池的大小(也称为核心线程数或线程池的最大线程数,具体取决于线程池的类型和配置)被设置为5,那么在同一时刻,线程池中最多只有五个线程会处于活动状态(即正在执行任务)。当线程池接收到新任务时,如果当前线程池中的线程数还没有达到5个,它会创建一个新线程来执行任务。然而,如果线程池中的线程数已经达到5个,并且这些线程都在忙碌地执行任务,那么新提交的任务将会被放入队列中等待,直到有线程完成其当前任务并变为空闲状态。
还需要注意线程池的一些其他配置和特性:
- 队列:线程池通常使用一个队列来存储等待执行的任务。当线程池中的线程都在忙碌时,新任务会被放入队列中。队列的大小和类型(例如,有界队列或无界队列)会影响线程池的行为。
- 拒绝策略:如果线程池中的线程都在忙碌,并且队列已满(如果使用了有界队列),那么当再有新任务提交给线程池时,线程池会调用其拒绝策略来处理这个任务。Java的
ThreadPoolExecutor
提供了几种不同的拒绝策略,如抛出异常、直接丢弃任务(这时需要你下次再执行的时候把任务重新加回来)、丢弃队列中最老的任务或者将任务交给调用线程来执行。线程池的默认拒绝策略是AbortPolicy
。当线程池无法处理新任务时(即线程池中的线程都在忙碌,且任务队列已满),AbortPolicy
会直接抛出一个RejectedExecutionException
异常。 - 最大线程数:除了核心线程数之外,
ThreadPoolExecutor
还允许设置一个最大线程数。当线程池中的线程数达到核心线程数时,如果队列也满了,线程池可以创建额外的线程(但不超过最大线程数)来处理新提交的任务。然而,这些额外的线程在空闲一段时间后(这个时间可以配置)会被终止,以将线程数缩减回核心线程数。