关于多线程Future使用过程中遇到的问题

公司一个新需求,统计一些数据,比如根据日期统计订单数量,订单总费用,均费用等,生成excel,定时发送给公司领导。数据库是mysql。根据日期,指的是按照比如前6个月,前1个月,前一天进行统计。由于订单量比较大,串行执行的话,效率比较低。所以在查询数据时就使用了多线程。因为多个日期查询出的数据需要汇总到一个excel,所以就使用了Future<Callable<>>来进行多线程操作,并获取结果集。

业务场景的废话说完了。

菜鸡楼主平时负责的业务线用到多线程的场景并不多,所以这次在具体使用的过程遇到的问题也是比较多。

第一步,先创建了一个抽象类,实现了Callable接口

public abstract class AbstractOrderThread implements Callable<List<T>> {
   @Override
	 public abstract List<OrderDataDTO> call();
}

第二步,

	public class ApiOperationThread extends AbstractOperationThread {
	// 可以添加私有属性,用来实现具体业务
	/**
	* 构造方法
	*/
	public ApiOperationThread() {
    
	}

@Override
public List<OrderOperationData> call() {
 		// TODO 具体业务(**注:一会儿这里会出现问题**)
 		// countDownLatch.countDown(); // 说到问题时,会说这里。
 }
}

第三步,有了可执行的类,就该初始化线程池了。

ExecutorService executorService = Executors.newCachedThreadPool(); //(阿里开发手册,不让用这种方式,需要自定义线程池)
有四种不同特性的线程池,可以自行百度。用CachedThreadPool其实有风险,但是该需求对并发要求不高,所以就用这个了。

第四步,有了线程池,就该创建线程,并且让线程池去将线程运行起来了

Future<List<T>> appOrderFuture = executorService.submit(new AppOrderThread());
Future<List<T>> apiOrderFuture = executorService.submit(new ApiOrderThread());
可创建多个Future放入线程池中执行(这就是多线程了)

第五步,统一获取返回结果(因为楼主需求是生成一个excel多个sheet,数据量大,所以使用阿里的easyExcel,但不支持多线程同时写多个sheet,所以只能将各个线程的结果集汇总,再串行写excel)

	try {
	     countDownLatch.await(); // 用了countDownLatch,没用Future.isDone(),具体用法……
	     List<T> appOrderList = appOrderFuture.get(); // Future.get()获取各个线程的返回值
	     result.addAll(appOrderList);
	     List<T> apiOrderList = apiOrderFuture.get();
	   	 result.addAll(apiOrderList); 
    } catch (InterruptedException | ExecutionException e) {
       e.printStackTrace();
       logger.error("中断异常:{}", e.getMessage());
    }

一大堆废话终于描述完了。说这么多,是记录一下自己实现需求的过程,备忘。同时也是希望有愿意看这些东西的同学能更好的理解场景,并根据自己的需求写出自己的实现。

问题来了,我在submit()之后,到了countDownLatch.await()时,发现一直阻塞,程序也没有抛出异常。原因就在于,在执行具体业务逻辑时,countDownLatch.countDown()并没有执行,导致countDownLatch.getCount()一直不为0。(countDownLatch.await 在countDownLatch.getCount为0时,才会停止阻塞)。

ok,直接原因找到了,但是由于没有抛异常,我找不到根本原因。得debug了。通过debug,定位了是一个查询没有能够执行,然后就到了FutureTask的源码了。sql没问题,xml和dao层也没毛病~还没不抛异常。

没招了,看源码吧。(同样问题的文章也有很多,但是由于当时楼主不知道该如何描述自己的问题,手动滑稽,所以就自己跟了源码)

线程是从submit之后开始执行的,进入submit

 /**
 * @throws RejectedExecutionException {@inheritDoc}
 * @throws NullPointerException       {@inheritDoc}
 */
 public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

执行了一个RunnableFuture,并返回了。看名字,大概理解就是一个可执行的Future。不管,先入了再说。

package java.util.concurrent;
/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the {@code run} method causes completion of the {@code Future}
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

原来里面也是继承了一个Runnable,就一个run()方法。熟悉吧。再入,看它怎么实现的(在FutureTask里)。

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

看到这儿,有点了然了。这个run方法catch到异常,通过setException(ex);方法给存起来了,并没输出~~ 所以要在具体业务代码里出现问题的地方,try-catch一下,看一下异常:多数据源配置的问题,至此问题解决。

源码看的比较马虎,只是粗糙的理解了大体意思。有深扒源码意愿的同学,移步其他大神文章,见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值