多线程处理集合后如何保序

背景

在批量接口调用时,我们不会选择循环遍历的串行调用方式,因为响应的时间会是所有调用时间之和,如查100个数据,每次查询50毫秒,循环调用串行的总时间时100*50=5000毫秒。

一般会选择使用批量查询接口或多线程来解决上述问题。但多线程由于并行性,受网络波动影响,会出现后执行的先得到返回结果的情况,在某些需要保序的场景,就需要关注结果的顺序和请求的顺序是否一致了。

举例

针对业务场景不同,举例两种方式:

  1. 使用List<Future<T>>保存线程池执行返回的Future。获取结果时,由于list是有序的,所以遍历List<Future<T>>得到的结果也是有序的,下面简单举例:

    ExecutorService executor=....;//线程池
    List<Integer> idList=...;//待查询的id
    List<someDto> resultList=new ArrayList();//存放查询的结果
    List<Future<someDto>> futureList=new ArrayList();//存放线程池查询的Future
    
    //此时,线程池是按idList的顺序遍历的,所以idList中顺序和futureList中顺序一致
    idList.forEach(id->{
    	futureList.add(executor.submit(()->{
    		return someService.queryById(id);
    }));
    });
    //此时是按照futureList的顺序遍历,所以resultList中的顺序和futureList顺序一致,也就和入参idList的顺序一致了
    futureList.forEach(future->{
    	resultList.add(future.get());
    });
  2. 使用Stream编程,stream使用CompletableFuture 实现多线程的使用,简单举例如下:

    ExecutorService executor=....;//线程池
    List<Integer> idList=...;//待查询的id
    List<someDto> resultList=new ArrayList();//存放查询的结果
    List<someDto> sortedList=new ArrayList();//存放保序后的结果
    
    //这里从idList.stream()...开始看,allOf是从最后的结果集取值。
    //这里先遍历每个id,并执行someService.queryById获取结果,并使用thenAccept指定得到结果后,将结果放入resultList中。
    //使用map将CompletableFuture.supplyAsync的返回结果集转为数组(toArray()),CompletableFuture.allOf的入参是数组类型。
    //最终使用get()获取所有的值,此时由于多线程响应的快慢不一样,所以顺序已经乱了。
    CompletableFuture.allOf(idList.stream()
    			.map(id -> CompletableFuture.supplyAsync(() -> someService.queryById(id), executorService).thenAccept(resultList::add))
    			.toArray(CompletableFuture[]::new))
    			.get();
    //这里将乱序的结果resultList,转换成Map,其中key是id,value是每个子项自己。
    Map<Integer,someDto>map=resultList.stream().collect(Collector.toMap(someDto::getId,dto->dto));
    //遍历入参idList,按照id从上述map中逐个get值,以达到保序的功能
    idList.forEach(id->{
    	sortedList.add(map.get(id));
    });

比较

不考虑性能,总体来看,stream的多线程用法似乎比传统的Future用法要复杂。但是如果业务场景不是简单的多线程使用,常规用法解决不了问题,拿第一个例子举例:

ExecutorService executor=....;//线程池
List<Integer> idList=...;//待查询的id
							......
//*************注意这里*************
//此处添加一个批量操作,先对idList做一次封装,封装后再用封装的结果执行多线程,如果这里调用的批量服务不能保序,如搜索,数仓等接口,用其默认的排序。
List<midDto> midList=midService.batchWrap(idList);
//再对midList做多线程操作,此时,多线程futureList中的顺序和批量操作返回的顺序相同,不再和idList一致
							......

此时,最终还是需要对结果集做一次保序处理。

大家有其他思路吗?欢迎留言分享一下~

猜你喜欢

转载自blog.csdn.net/u013821237/article/details/104449735
今日推荐