Java8 parallelStream实战

项目环境:

JDK12

springboot:2.1.6.RELEASE

springcloud:Greenwich.RELEASE

业务场景

系统需要对接RFID,扫描枪扫描商品得到的EPCCode经过前端传到后端,后端API需要先将EPC通过算法转成EANCode,再用EANcode请求一个API,得到itemcode。

由于商品可能有多个,且最多有16个,如果用串行的话,由于EPC转EAN的算法很复杂,而且从EAN转itemcode还需要请求外部的API,效率会很低,所以并行是必须的。

解决方案

那么问题来了,并行的解决方案有很多,之前使用过多线程配合CountDownLatch可以做到,但是代码量太大而且代码可读性太差了,刚好最近在研究的parallelStream可以解决这个问题,于是就实践一下,本地和测试环境跑下来还可以,就等上生产看效果啦!

首先,parallelStream的适用场景是CPU密集型的操作,充分利用CPU,在我们这个业务场景中,EPC转EAN需要大量的计算,对CPU依赖大,而EAN转itemcode这一步需要请求外部API,这里主要就是等待响应,让他们一起等着几好啦。因为我们是需要将我们的结果同步返回给前端所以,这里我们不能使用execute而要使用submit,在后面get一下,保证将所有任务做完将全部结果返回给前端。

相信大家都知道,parallelStream使用的是ForkJoin框架的ForkJoinPool,而ForkJoinPool默认开启的线程数是你机器CPU的核心数,我电脑是8核的,所以它会同时开启8个线程来执行我的任务,但是我们的服务器是2核的,所以如果不对它进行修改的话,它以此最多启用2个线程来执行我们的任务,这显然不是我们想要的,所以我在这里创建了一个核心线程数为16的ForkJoinPool,为什么是16呢?因为我们的订一个订单中,商品最多是16,为了使线程够用,就设为了16.

大家可能注意到了,这里我用了Collections.synchronizedList()给ArrayList包装了一下,因为这个list我们是需要多线程add的,而ArrayList并不是一个线程安全的集合,Collections.synchronizedList()可以帮它做到这点。

一开始,我想着要不要也将ArrayList初始化为16的长度,来避免它扩容,但是,后来一想还是不用了,因为ArrayList默认长度为10,一般订单商品是不会超过这个值的,这样做的话并不会减少响应时间反而会浪费空间。

当然,我在getItemCodeByEpcCode方法里是加了个缓存的,因为每个EPC转成itemcode几乎是不会改变的嘛,所以设了一个月的过期时间。

具体实现

让我们直接来看下代码实现(具体的过程在getItemCodeByEpcCode这个方法里面,因为跟我们的主题不大,所以就不贴出来了):

@Override
public ApiResponseDto getItemCodeByEpcCode(List epcCodeList) throws Exception {
    List results = Collections.synchronizedList(new ArrayList<>());

    //get httpHeader
    HttpHeaders headers = tokenFeign.clientCredentialHttpHeader(BeanConstant.SYSTEM_CODE_MASTERDATAS).getHeaders();

    log.info("[GetItemCodeByEpcCode API] start to get itemCode for [ {} ]", epcCodeList.toString());

    ForkJoinTask submit = new ForkJoinPool(16).submit(() -> epcCodeList.parallelStream().forEach(epcCode -> {
        RfidResponseDto itemCode = getItemCodeByEpcCode(epcCode, headers);
        results.add(itemCode);
    }));

    submit.get();

    return new ApiResponseDto<>(AppClientResponse.GENERAL_SUCC, results);
}

你可能感兴趣的:(线程,java,多线程,stream,parallelstream)