Futures在Java 5(2004)中引入。它们是承诺在操作完成后保留操作结果的对象。调用者可以使用future对象来检查操作isDone(),或者等待它完成使用get()。Future模式一个最大的问题是何时调用问题(过早地阻塞Future.get(),这消除了异步执行的好处)。spring的 ListenableFuture给出了相应的解决方案,本文将就此展开对比。
以 jdk1.8 和 Spring Framework 4.3.4.RELEASE 为基准
本文详细分析 Spring 中的 ThreadPoolTaskExecutor 与 ListenableFutureTask
对象;并且比较 ThreadPoolTaskExecutor 和 ThreadPoolExecutor 之间的区别。
介绍threadPoolTaskExecutor 的基本使用
比较 ListenableFuture 与 Future
深入解析ListenableFuture 对象
ThreadPoolExecutor 是 JDK 自带,ThreadPoolTaskExecutor 是 Spring 在 ThreadPoolExecutor 的基础上进行了一层封装。
java.util.concurrent.ThreadPoolExecutor
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
相比 ThreadPoolExecutor,ThreadPoolTaskExecutor 增加了 submitListenable 方法,该方法返回 ListenableFuture 接口对象,该接口完全抄袭了 google 的 guava。
ListenableFuture 接口对象,增加了线程执行完毕后成功和失败的回调方法。从而避免了 Future 需要以阻塞的方式调用 get,然后再执行成功和失败的方法。
ThreadPoolTaskExecutor 中具体的初始化线程池方法如下:
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor;
if (this.taskDecorator != null) {
executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler) {
@Override
public void execute(Runnable command) {
super.execute(taskDecorator.decorate(command));
}
};
}
else {
executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler);
}
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
具体使用如下:
public static void main(String[] args) throws Exception {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setCorePoolSize(2);
executorService.setMaxPoolSize(2);
executorService.setKeepAliveSeconds(60);
executorService.setQueueCapacity(Integer.MAX_VALUE);
executorService.initialize();
executorService.submitListenable(() -> {
// 休息 5 秒,模拟工作的情况
TimeUnit.SECONDS.sleep(5);
// 通过抛出 RuntimeException 异常来模拟异常
//throw new RuntimeException("出现异常");
return true;
}).addCallback(data -> logger.info("success,result = {}", data), ex -> logger.info("**异常信息**:{}", ExceptionUtils.getExceptionMsg(ex)));
}
org.springframework.util.concurrent.ListenableFuture
java.util.concurrent.Future
ListenableFuture 接口继承 Future 接口,并增加了如下两个方法:
void addCallback(ListenableFutureCallback<? super T> callback);
void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
ExecutorService executor = getThreadPoolExecutor();
try {
ListenableFutureTask<T> future = new ListenableFutureTask<T>(task);
executor.execute(future);
return future;
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
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);
}
}
@Override
protected void done() {
Throwable cause;
try {
T result = get();
this.callbacks.success(result);
return;
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return;
}
catch (ExecutionException ex) {
cause = ex.getCause();
if (cause == null) {
cause = ex;
}
}
catch (Throwable ex) {
cause = ex;
}
this.callbacks.failure(cause);
}
ListenableFuture 相比 Future 是不需要知道 执行结果的情况下就可以将 成功或者失败的业务代码 通过回调的方式 预埋,带来的好处就是异步,不需要阻塞当前线程,从而可以提高系统的吞吐量;
Future 需要通过 get() 方法阻塞当前线程,在获取线程的执行结果后再根据执行结果编写相关的业务代码;
下面通过一个例子来演示下:
public static void main(String[] args) {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setCorePoolSize(1);
executorService.setMaxPoolSize(1);
executorService.initialize();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
ListenableFuture<Boolean> asyncResult = executorService.submitListenable(() -> {
// 休息5毫秒,模拟执行
TimeUnit.MILLISECONDS.sleep(5);
//throw new RuntimeException("出现异常");
return true;
});
asyncResult.addCallback(data -> {
try {
// 休息3毫秒模拟获取到执行结果后的操作
TimeUnit.MILLISECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}, ex -> logger.info("**异常信息**:{}", ExceptionUtils.getExceptionMsg(ex)));
}
System.out.println(String.format("总结耗时:%s", System.currentTimeMillis() - start));
}
通过测试3次,总计耗时在 50 - 60 毫秒之间
public static void main(String[] args) {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setCorePoolSize(1);
executorService.setMaxPoolSize(1);
executorService.initialize();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
Future<Boolean> future = executorService.submit(() -> {
try {
// 休息5毫秒,模拟执行
TimeUnit.MILLISECONDS.sleep(5);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
});
try {
// 以阻塞的方式获取执行结果
Boolean result = future.get();
// logger.info(String.format("执行结果:%s", result));
// 休息3毫秒模拟获取到执行结果后的操作
TimeUnit.MILLISECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(String.format("总结耗时:%s", System.currentTimeMillis() - start));
}