并发处理并将处理结果整合

    前段时间优化公司代码时发现公司以前有部分代码效率奇低。调查之后发现,以前的同事将一个返回值分为5个方法单线程获取,部分代码如下:

        List<ActivityHistoryBean> actHisList = new ArrayList<>();
        actHisList.addAll(getAssetApproveList(drId));
        actHisList.addAll(getAssetFansList(drId));
        actHisList.addAll(getAssetDoctorList(drId));
        actHisList.addAll(getAssetPickList(drId));
        actHisList.addAll(getAssetManualList(drId));
        actHisList.addAll(getAssetOrderMeList(drId));
        actHisList.addAll(getAssetOrderFriendList(drId));
        actHisList.addAll(getAssetGourdList(drId));

里面的每一个方法都还要到数据库里去读取一次数据,这样的一串代码执行下来需要2分钟以上。这对于一个接口来说,响应时间简直就是噩梦级的,不知道他们以前时如何忍受下来的。

    由于效率低,于是我开始着手进行优化。首先我发现所有方法等返回值都一样,那么我想到的第一个点就是线程池,把所有的方法往线程池里一抛,等线程全部结束之后我再将结果合并返回。要想将并发线程的执行结果合并就需要有一个线程工具类,并且我更乐意将这个工具做的更加通用以便以后其他地方也可以用到。于是我开始着手开发线程控制工具类。

    第一步,我需要一个方法,参数是一个Excutor接口加一个List<Callable>,这两个组成了我们要完成任务的核心部分,当然,并发少不了超时时间Integer和时间单位TimeUnit:

public static <T> List<T> submitAndWait(Executor executor, List<Callable<T>> solvers, Integer timeout, TimeUnit unit, Integer sleepTime) {
    return null;
}

这里用到泛型是为了让这个方法更加通用,这里大家可能会奇怪为什么还多了一个sleepTime参数,不要急,暂时忽略这个参数,后面我们会讲到它。

    第二步,我需要一个批处理接口,因为我要将最后的结果做合并处理。所以在这里我用到了ExecutorCompletionService,这个对象会将每个线程的执行结果放入一个队列中等候我的调遣。

CompletionService<T> completionService = new ExecutorCompletionService<>(executor);

    最后,开始具体的调用。首先是要将入参里的solvers挨个儿放入completionService中进行并发处理。然后定时循环获取completionService中的结果。因为take()方法会阻塞线程,而poll()方法不会阻塞线程,为了保证线程不被阻塞,所以这里我使用的是poll()方法,但是使用poll()会有一个问题,那就是CPU占用问题。如果不对poll()加以限制的话,CPU占用率很快就会飙到100%,所以我想到的解决方案是每次poll()执行结束后让线程sleep一段时间,但是我又不想将具体的睡眠时间写死在方法里,所以就有了前面的sleepTime参数,这个参数就是用来控制睡眠时间的,调用者可以根据自己的实际情况设定睡眠时长。

        while (count < n && waitCnt < waitMax) {
            try {
                TimeUnit.MILLISECONDS.sleep(sleepTime);
                waitCnt += TimeUnit.MILLISECONDS.toSeconds(sleepTime);
                for (int i = count; i < n; ++i) {
                    Future<T> poll = completionService.poll();
                    if (poll != null) {
                        T t = poll.get();
                        returnList.add(t);
                        count++;
                    }
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

n是List<Callable>的大小,这里的count是一个计数器,我们知道List有多大,返回的结果就有多少条,所以如果count<n的话证明还有的线程没有执行完,那么我们还需要继续获取队列中的消息。waitCnt是记录poll()消耗了多少时间,waitMax就是设定的超时时间,如果小于超时时间,那么可以继续获取。因为poll()每次去到的是队列中第一个元素(先进先出)所以还有多少没有获取到我们就循环poll多少次。如果poll出来不为null那么就加入返回列表。

当上面的流程执行完后返回returnList,并发处理结束。

经测试,通过这样的方法程序效率可以提高很多。

附完整代码,以便参考:

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * Description: 并发工具类
 * Designer: jack
 * Date: 2017/6/22
 * Version: 1.0.0
 */
@Slf4j
public class ThreadUtil {

    public static <T> List<T> submitAndWait(Executor executor, List<Callable<T>> solvers, Integer timeout, TimeUnit unit, Integer sleepTime) {
        long threadStart = System.currentTimeMillis();
        List<T> returnList = new ArrayList<>();
        int n = solvers.size();
        int count = 0;
        long waitMax = unit.toSeconds(timeout);
        long waitCnt = 0;
        CompletionService<T> completionService = new ExecutorCompletionService<>(executor);
        for (Callable<T> callable : solvers) {
            completionService.submit(callable);
        }
        while (count < n && waitCnt < waitMax) {
            try {
                TimeUnit.MILLISECONDS.sleep(sleepTime);
                waitCnt += TimeUnit.MILLISECONDS.toSeconds(sleepTime);
                for (int i = count; i < n; ++i) {
                    Future<T> poll = completionService.poll();
                    if (poll != null) {
                        T t = poll.get();
                        returnList.add(t);
                        count++;
                    }
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        long threadCost = System.currentTimeMillis() - threadStart;
        log.info("thread util Cost: {}", threadCost);
        return returnList;
    }

}

 

转载于:https://my.oschina.net/jack90john/blog/1494714

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值