【并发编程】Guava - ListenableFuture,避免Future获取阻塞问题,增加回调

1. 简介#

  相比Future(【并发编程】Java5 - Future,基本使用),Guava提供的ListenableFuture支持不阻塞主线程进行任务执行完成后的业务处理。
  使用Future的实现类FutureTask想要实现一旦获取到结果立即执行后续的业务,就需要阻塞主线程等待结果或者使用其他线程循环的判断任务是否结束,这样导致性能较低,且代码负责。ListenableFuture在Future的基础上增加了任务执行后自动调用后续业务处理的逻辑,方便我们使用。

2. ListenableFuture回调函数#

  • 调用ListenableFuture接口的addListener(Runnable listener, Executor executor)方法,其中第一个参数为回调函数的处理逻辑,第二个运行监听器的线程池,一般使用执行任务的线程池
Copy
public interface ListenableFuture<V extends @Nullable Object> extends Future<V> { /** * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. * The listener will run when the {@code Future}'s computation is {@linkplain Future#isDone() * complete} or, if the computation is already complete, immediately. * * <p>There is no guaranteed ordering of execution of listeners, but any listener added through * this method is guaranteed to be called once the computation is complete. * * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception * thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and * logged. * * <p>Note: If your listener is lightweight -- and will not cause stack overflow by completing * more futures or adding more {@code directExecutor()} listeners inline -- consider {@link * MoreExecutors#directExecutor}. Otherwise, avoid it: See the warnings on the docs for {@code * directExecutor}. * * <p>This is the most general listener interface. For common operations performed using * listeners, see {@link Futures}. For a simplified but general listener interface, see {@link * Futures#addCallback addCallback()}. * * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"> * <i>happen-before</i></a> its execution begins, perhaps in another thread. * * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners * after executing them. * * @param listener the listener to run when the computation is complete * @param executor the executor to run the listener in * @throws RejectedExecutionException if we tried to execute the listener immediately but the * executor rejected it. */ void addListener(Runnable listener, Executor executor); }
  • 调用Futures类的静态方法addCallback(final ListenableFuture future, final FutureCallback<? super V> callback, Executor executor)方法,其中第一个参数为任务执行后的Future,第二个为回调函数,第三个为运行回调函数的线程池,一般使用执行任务的线程池
Copy
public final class Futures extends GwtFuturesCatchingSpecialization { // ...... /** * Registers separate success and failure callbacks to be run when the {@code Future}'s * computation is {@linkplain java.util.concurrent.Future#isDone() complete} or, if the * computation is already complete, immediately. * * <p>The callback is run on {@code executor}. There is no guaranteed ordering of execution of * callbacks, but any callback added through this method is guaranteed to be called once the * computation is complete. * * <p>Exceptions thrown by a {@code callback} will be propagated up to the executor. Any exception * thrown during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an * exception thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught * and logged. * * <p>Example: * * <pre>{@code * ListenableFuture<QueryResult> future = ...; * Executor e = ... * addCallback(future, * new FutureCallback<QueryResult>() { * public void onSuccess(QueryResult result) { * storeInCache(result); * } * public void onFailure(Throwable t) { * reportError(t); * } * }, e); * }</pre> * * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. * * <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link * ListenableFuture#addListener addListener}. * * @param future The future attach the callback to. * @param callback The callback to invoke when {@code future} is completed. * @param executor The executor to run {@code callback} when the future completes. * @since 10.0 */ public static <V extends @Nullable Object> void addCallback( final ListenableFuture<V> future, final FutureCallback<? super V> callback, Executor executor) { Preconditions.checkNotNull(callback); future.addListener(new CallbackListener<V>(future, callback), executor); } }

3. ListenableFuture应用#

  • 前期准备:实现Callable创建线程,支持指定执行时间,并增加执行时间校验逻辑
Copy
/** * 创建线程 */ @RequiredArgsConstructor class MyCallable implements Callable<Long> { /** * 任务执行时间 */ private final long execTime; @Override public Long call() throws Exception { if (execTime <= 0) { throw new RuntimeException("执行时间必须大于0"); } log.info("任务执行,耗时:" + execTime + " ms"); Thread.sleep(execTime); return execTime; } }
  • 引入Guava依赖
Copy
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency>
  • 增加监听器
Copy
/** * 增加监听器 */ @Test public void testAddListener() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); ListenableFuture<Long> future = listeningExecutorService.submit(new MyCallable(1000)); // 增加监听器 future.addListener(new Runnable() { @Override public void run() { try { log.info("任务执行结束,结果为:" + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(3000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
19:18:29.256 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 19:18:30.263 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 任务执行结束,结果为:1000
  • 测试任务执行成功回调
Copy
/** * 测试任务执行成功回调 */ @Test public void addCallbackOfSuccess() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); ListenableFuture<Long> future = listeningExecutorService.submit(new MyCallable(1000)); // 增加回调 FutureCallback<Long> futureCallback = new FutureCallback<Long>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(Long result) { try { log.info("任务执行成功,结果为:" + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(future, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(3000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
19:19:34.162 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 19:19:35.173 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 任务执行成功,结果为:1000
  • 测试任务执行失败回调
Copy
/** * 测试任务执行失败回调 */ @Test public void addCallbackOfFailure() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); ListenableFuture<Long> future = listeningExecutorService.submit(new MyCallable(-1)); // 增加回调 FutureCallback<Long> futureCallback = new FutureCallback<Long>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(Long result) { try { log.info("任务执行成功,结果为:" + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(future, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(3000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
19:21:29.158 [pool-1-thread-1] ERROR com.c3stones.test.ListenableFutureTest - 任务执行失败,异常原因:执行时间必须大于0
  • 测试所有任务执行成功并获取结果集
Copy
/** * 测试所有任务执行成功并获取结果集 * * @throws InterruptedException */ @Test public void testAsListSuccess() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); // 创建3个任务,3个都成功 ListenableFuture<Long> future1 = listeningExecutorService.submit(new MyCallable(1000)); ListenableFuture<Long> future2 = listeningExecutorService.submit(new MyCallable(2000)); ListenableFuture<Long> future3 = listeningExecutorService.submit(new MyCallable(3000)); ListenableFuture<List<Long>> future = Futures.allAsList(future1, future2, future3); // 增加回调 FutureCallback<List<Long>> futureCallback = new FutureCallback<List<Long>>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(List<Long> result) { try { log.info("任务执行成功,结果为:" + future.get().stream().map(String::valueOf).collect(Collectors.joining(","))); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(future, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(5000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
20:11:13.057 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:2000 ms 20:11:13.051 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 20:11:13.059 [pool-1-thread-3] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:3000 ms 20:11:16.063 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 任务执行成功,结果为:1000,2000,3000
  • 测试包含失败任务执行并获取结果集
Copy
/** * 测试包含失败任务执行并获取结果集 * * @throws InterruptedException */ @Test public void testAsListFailure() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); // 创建3个任务,2个都成功,1个失败 ListenableFuture<Long> future1 = listeningExecutorService.submit(new MyCallable(1000)); ListenableFuture<Long> future2 = listeningExecutorService.submit(new MyCallable(-2)); ListenableFuture<Long> future3 = listeningExecutorService.submit(new MyCallable(3000)); ListenableFuture<List<Long>> future = Futures.allAsList(future1, future2, future3); // 增加回调 FutureCallback<List<Long>> futureCallback = new FutureCallback<List<Long>>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(List<Long> result) { try { log.info("任务执行成功,结果为:" + future.get().stream().map(String::valueOf).collect(Collectors.joining(","))); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(future, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(5000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
20:12:51.029 [pool-1-thread-3] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:3000 ms 20:12:51.030 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 20:12:51.091 [pool-1-thread-2] ERROR com.c3stones.test.ListenableFutureTest - 任务执行失败,异常原因:执行时间必须大于0

  通过结果可以看出,只要有任务失败,则不会执行回调方法中成功的处理逻辑,而是仅执行回调方法中失败的处理逻辑。适用于任务不会失败,或者专门获取任意失败任务时的场景。

  • 测试所有任务执行成功或失败获取结果,失败结果替换为null
Copy
/** * 测试所有任务执行成功或失败获取结果,失败结果替换为null * * @throws InterruptedException */ @Test public void testSuccessfulAsList() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); // 创建3个任务,2个都成功,1个失败 ListenableFuture<Long> future1 = listeningExecutorService.submit(new MyCallable(1000)); ListenableFuture<Long> future2 = listeningExecutorService.submit(new MyCallable(-2)); ListenableFuture<Long> future3 = listeningExecutorService.submit(new MyCallable(3000)); ListenableFuture<List<Long>> future = Futures.successfulAsList(future1, future2, future3); // 增加回调 FutureCallback<List<Long>> futureCallback = new FutureCallback<List<Long>>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(List<Long> result) { try { log.info("任务执行成功,结果为:" + future.get().stream().map(String::valueOf).collect(Collectors.joining(","))); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(future, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(5000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
20:16:10.092 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 20:16:10.092 [pool-1-thread-3] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:3000 ms 20:16:13.104 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行成功,结果为:1000,null,3000
  • 测试返回结果同步转换
Copy
/** * 测试返回结果同步转换 */ @Test public void testTransform() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); ListenableFuture<Long> future1 = listeningExecutorService.submit(new MyCallable(1000)); // 将返回结果转换为字符串 ListenableFuture<String> transform = Futures.transform(future1, new Function<Long, String>() { @Override public String apply(Long input) { String result = String.valueOf(input); log.info("将Long[" + input + "] 转换为 String[" + result + "]"); return "String -> " + result; } }, listeningExecutorService); // 增加回调 FutureCallback<String> futureCallback = new FutureCallback<String>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(String result) { log.info("任务执行成功,结果为:" + result); } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(transform, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(3000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
20:40:11.661 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 20:40:12.830 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 将Long[1000] 转换为 String[1000] 20:40:12.832 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行成功,结果为:String -> 1000
  • 测试返回结果异步转换
Copy
/** * 测试返回结果异步转换 */ @Test public void testTransformAsync() throws InterruptedException { ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); ListenableFuture<Long> future1 = listeningExecutorService.submit(new MyCallable(1000)); // 将返回结果转换为字符串 ListenableFuture<String> transform = Futures.transformAsync(future1, new AsyncFunction<Long, String>() { @Override public ListenableFuture<String> apply(Long input) throws Exception { String result = String.valueOf(input); log.info("将Long[" + input + "] 转换为 String[" + result + "]"); return Futures.immediateFuture("String -> " + result); } }, listeningExecutorService); // 增加回调 FutureCallback<String> futureCallback = new FutureCallback<String>() { /** * 成功时调用 * @param result */ @Override public void onSuccess(String result) { log.info("任务执行成功,结果为:" + result); } /** * 失败时调用 * @param t */ @Override public void onFailure(Throwable t) { log.error("任务执行失败,异常原因:" + t.getMessage()); } }; Futures.addCallback(transform, futureCallback, listeningExecutorService); // 为了验证测试结果,阻塞主线程,等待监听器任务异步执行 // 真实使用不需要阻塞主线程 Thread.sleep(3000); listeningExecutorService.shutdown(); }

  控制台打印:

Copy
20:42:02.687 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行,耗时:1000 ms 20:42:03.697 [pool-1-thread-2] INFO com.c3stones.test.ListenableFutureTest - 将Long[1000] 转换为 String[1000] 20:42:03.699 [pool-1-thread-1] INFO com.c3stones.test.ListenableFutureTest - 任务执行成功,结果为:String -> 1000

4. 项目地址#

  thread-demo

posted @   C3Stones  阅读(1126)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2017-03-28 蓝桥杯 历届试题 PREV-1 核桃的数量
点击右上角即可分享
微信分享提示
CONTENTS