JDK8中的默认线程池

一、背景

前期开发了一个列表查询接口,返回内容中有一些需要去查询C服务来补全内容。为了提高列表查询接口的性能,需要对列表中的内容进行并发查询C服务来缩短全量列表任务的执行时间。

因为JDK8中有并行流处理方法,此处是对列表中的内容进行查询补齐,使用并行流处理方式正好能够完成这个任务,于是就使用了parallelStream来处理。伪代码如下:

    public List<Detail> list(){
        //从DB中查询到数据
        List<Detail> detailList = Stream.of(Detail.builder().id("1").name("name1").build(),
                Detail.builder().id("2").name("name2").build(),
                Detail.builder().id("3").name("name3").build(),
                Detail.builder().id("4").name("name4").build(),
                Detail.builder().id("5").name("name5").build(),
                Detail.builder().id("6").name("name6").build(),
               Detail.builder().id("7").name("name7").build()).collect(Collectors.toList());
        log.info("Detail get, size is: {}", detailList.size());
        detailList.parallelStream().forEach(
                detail -> {
                    log.info("userId:{}", BlogRequestContext.getCurrentContext().getUserId());
                    try {
                        // query C server to get detail
                        Thread.sleep(3000);
                    }catch (Exception e){}
                    detail.setUserId(BlogRequestContext.getCurrentContext().getUserId());
                }
        );
        return detailList;
    }

parallelStream提供了比较方便的列表并行处理方式,当时在测试环境验证通过后就上线了。运行一段时间后出现了一个用户查询列表后,数据中C服务内容缺失了,但实际改用户的资源在C服务中是有记录的。

二、问题分析

代码中查询C服务时,会传入用户信息,通过用户信息及其他条件来查询当前用户在C服务的数据。

通过排查日志,发现在请求C服务时,传入的用户信息是另一位用户的,用户信息串用了。

而代码中的用户信息是通过线程上下文传递的,在主线程中用户信息没有串,但是通过parallelStream处理的线程就出现了线程上下文串用的情况,究其原因,还是线程池在处理完任务后,线程没有被立即回收,同时又被调度去处理其他的任务了,此时线程里的上下文信息还是上一个任务的,导致上下文串用了。

至此基本定位到问题了。

那解决方案也就出来了,也就是在用parallelStream时,从主线程将上下文信息拿出来,在parallelStream的子线程中处理时,将这些主线程的上下文再重新reset一下,保证子线程的上下文和主线程的一致。

三、parallelStream的线程池是怎么样的

默认情况下,parallelStream使用的是ForkJoinPool.commonPool(),这是一个共用的线程池,也就是工程的JVM都共用这个公共线程池,除了parallelStream外,还有completablefuture也使用了这个公共线程池。

这个公共线程池,线程池数量是当前CPU核数-1,减1是因为当前主线程会占用一个线程,线程池中的CPU核数-1如果是大于1的,就会新建这么个公共线程池,如果小于等于1的话,系统就会新建一个线程来处理,而不是线程池。

如果是新建一个线程来处理,并发量特别大的情况,工程会不断的新建线程,可能会导致线程资源耗尽。

总结:parallelStream和completablefuture都会使用ForkJoinPool.commonPool()的公共线程池。

四、线程池中如何避免线程串用

工程中自定义的线程池也会存在线程串用的情况,一般我们的解决办法是使用阿里巴巴的transmittable-thread-local依赖,在定义线程池时进行设置。

int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("bService_");
        executor.setCorePoolSize(processors);
        executor.setMaxPoolSize(processors << 1);
        executor.setKeepAliveSeconds(30);
        executor.setAllowCoreThreadTimeOut(true);
        executor.initialize();
        return TtlExecutors.getTtlExecutor(executor);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值