公司一个新需求,统计一些数据,比如根据日期统计订单数量,订单总费用,均费用等,生成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一下,看一下异常:多数据源配置的问题,至此问题解决。
源码看的比较马虎,只是粗糙的理解了大体意思。有深扒源码意愿的同学,移步其他大神文章,见谅。