记录下并行优化时的一个问题

    最近在做模块性能优化时,尝试将请求外部模块和操作数据库这种耗时操作又非前后关联的动作改为并行,用到了CompletableFuture.runAsync,遍历复杂集合时修改为parallelStream;

    实际上parallelStream和CompletableFuture.runAsync的并行都是使用了Fork/join的线程池来处理,可以参考http://blog.dyngr.com/blog/2016/09/15/java-forkjoinpool-internals/, 既然是用这个线程池,那么我们肯定会面临2个问题:
1.会有线程上下文切换,处理线程到fork/join池中线程的切换,是否有参数必须传递过去;
2.使用的fork/join池是jvm全局共用,所有并行流调用和所有的CompletableFuture.runAsync都会用到,大小是cpu个数,如果竞争,反而会降低性能(所以很多链接都会说,cpu密集型适合用并行流,比如不少实测也是计算型测试,性能确实好,但是实际上大多数业务都不是cpu密集型任务)。

对第一个问题可能的影响,以我们业务为例,ServiceComb有个上下文CseContext,在rpc调用时是塞到x-cse-context的http头里的,线程切换时如果不设置则不会携带到下游模块导致报错,解决方法也简单:

InvocationContext context = ContextUtils.getInvocationContext();

然后在并行流中开始遍历前或者CompletableFuture.runAsync或者thenRunAsync方法首先设置一下上下文

ContextUtils.setInvocationContext(context);

对第二个问题,对并行流和CompletableFuture.runAsync的处理也是不一样的,并行流处理可参考 http://www.importnew.com/16801.html 

ForkJoinTask task = forkJoinPool.submit(() -> conditions.parallelStream().forEach(condition ->
{
   xx
}));
xx;
task.join();

对CompletableFuture.runAsync 则是传入自定义的ExecutorService对象来防止系统限制的大小

final AtomicInteger threadId = new AtomicInteger(0);

private ExecutorService executor =
    new ThreadPoolExecutor(100, 100, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(100),
        r -> new Thread(r, "rule-parallel-thread-" + threadId.getAndIncrement()));

调用时

InvocationContext context = ContextUtils.getInvocationContext();
CompletableFuture.runAsync(() ->
{
    ContextUtils.setInvocationContext(context);
   xxx
}, executor).thenRunAsync(() ->
{
    ContextUtils.setInvocationContext(context);
    xxx
}, executor).thenRunAsync(() ->
{
    ContextUtils.setInvocationContext(context);
    xxx
}, executor).join();

坐等性能测试结果,调整线程池大小

你可能感兴趣的:(Java行进中,面试&&经历)