How to maintain order after multithreading processing collection

background

In the batch interface call, we will not choose the serial call method of cyclic traversal, because the response time will be the sum of all call times, such as checking 100 data, each query is 50 milliseconds, and the total time of cyclic calling serial 100*50=5000 milliseconds.

Generally choose to use batch query interface or multi-thread to solve the above problems. However, due to the parallelism of multithreading and affected by network fluctuations, the results will be returned first after execution. In some scenarios where order is required, it is necessary to pay attention to whether the order of results is consistent with the order of requests.

For example

For different business scenarios, two examples are given:

  1. Use List<Future<T>> to save the Future returned by the thread pool execution. When the result is obtained, since the list is ordered, the result obtained by traversing List<Future<T>> is also ordered. The following simple example:

    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. Use Stream programming, stream uses CompletableFuture to achieve multi-threaded use, a simple example is as follows:

    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));
    });

     

Compare

Regardless of performance, overall, the multi-threaded usage of stream seems to be more complicated than the traditional Future usage. But if the business scenario is not a simple multi-threaded use, conventional usage cannot solve the problem. Take the first example as an example:

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

At this point, it is necessary to do a preserving processing of the result set.

Do you have other ideas? Welcome to leave a message to share~

Guess you like

Origin blog.csdn.net/u013821237/article/details/104449735