【面试专栏】Guava - ListenableFuture,避免Future获取阻塞问题,增加回调

1. 简介

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

2. ListenableFuture回调函数

  • 调用ListenableFuture接口的addListener(Runnable listener, Executor executor)方法,其中第一个参数为回调函数的处理逻辑,第二个运行监听器的线程池,一般使用执行任务的线程池
public interface ListenableFuture extends Future {
  /**
   * 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.
   *
   * 

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. * *

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. * *

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}. * *

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()}. * *

Memory consistency effects: Actions in a thread prior to adding a listener * happen-before its execution begins, perhaps in another thread. * *

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 callback, Executor executor)方法,其中第一个参数为任务执行后的Future,第二个为回调函数,第三个为运行回调函数的线程池,一般使用执行任务的线程池
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.
   *
   * 

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. * *

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. * *

Example: * *

{@code
   * ListenableFuture future = ...;
   * Executor e = ...
   * addCallback(future,
   *     new FutureCallback() {
   *       public void onSuccess(QueryResult result) {
   *         storeInCache(result);
   *       }
   *       public void onFailure(Throwable t) {
   *         reportError(t);
   *       }
   *     }, e);
   * }
* *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. * *

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 void addCallback( final ListenableFuture future, final FutureCallback callback, Executor executor) { Preconditions.checkNotNull(callback); future.addListener(new CallbackListener(future, callback), executor); } }

3. ListenableFuture应用

  • 前期准备:实现Callable创建线程,支持指定执行时间,并增加执行时间校验逻辑
/**
 * 创建线程
 */
@RequiredArgsConstructor
class MyCallable implements Callable {

    /**
     * 任务执行时间
     */
    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依赖

    com.google.guava
    guava
    31.1-jre
  • 增加监听器
/**
 * 增加监听器
 */
@Test
public void testAddListener() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    ListenableFuture 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();
}

  控制台打印:

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
  • 测试任务执行成功回调
/**
 * 测试任务执行成功回调
 */
@Test
public void addCallbackOfSuccess() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    ListenableFuture future = listeningExecutorService.submit(new MyCallable(1000));

    // 增加回调
    FutureCallback futureCallback = new FutureCallback() {

        /**
         * 成功时调用
         * @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();
}

  控制台打印:

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
  • 测试任务执行失败回调
/**
 * 测试任务执行失败回调
 */
@Test
public void addCallbackOfFailure() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    ListenableFuture future = listeningExecutorService.submit(new MyCallable(-1));

    // 增加回调
    FutureCallback futureCallback = new FutureCallback() {

        /**
         * 成功时调用
         * @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();
}

  控制台打印:

19:21:29.158 [pool-1-thread-1] ERROR com.c3stones.test.ListenableFutureTest - 任务执行失败,异常原因:执行时间必须大于0
  • 测试所有任务执行成功并获取结果集
/**
 * 测试所有任务执行成功并获取结果集
 *
 * @throws InterruptedException
 */
@Test
public void testAsListSuccess() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    // 创建3个任务,3个都成功
    ListenableFuture future1 = listeningExecutorService.submit(new MyCallable(1000));
    ListenableFuture future2 = listeningExecutorService.submit(new MyCallable(2000));
    ListenableFuture future3 = listeningExecutorService.submit(new MyCallable(3000));

    ListenableFuture> future = Futures.allAsList(future1, future2, future3);

    // 增加回调
    FutureCallback> futureCallback = new FutureCallback>() {

        /**
         * 成功时调用
         * @param result
         */
        @Override
        public void onSuccess(List 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();
}

  控制台打印:

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
  • 测试包含失败任务执行并获取结果集
/**
 * 测试包含失败任务执行并获取结果集
 *
 * @throws InterruptedException
 */
@Test
public void testAsListFailure() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    // 创建3个任务,2个都成功,1个失败
    ListenableFuture future1 = listeningExecutorService.submit(new MyCallable(1000));
    ListenableFuture future2 = listeningExecutorService.submit(new MyCallable(-2));
    ListenableFuture future3 = listeningExecutorService.submit(new MyCallable(3000));

    ListenableFuture> future = Futures.allAsList(future1, future2, future3);

    // 增加回调
    FutureCallback> futureCallback = new FutureCallback>() {

        /**
         * 成功时调用
         * @param result
         */
        @Override
        public void onSuccess(List 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();
}

  控制台打印:

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
/**
 * 测试所有任务执行成功或失败获取结果,失败结果替换为null
 *
 * @throws InterruptedException
 */
@Test
public void testSuccessfulAsList() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    // 创建3个任务,2个都成功,1个失败
    ListenableFuture future1 = listeningExecutorService.submit(new MyCallable(1000));
    ListenableFuture future2 = listeningExecutorService.submit(new MyCallable(-2));
    ListenableFuture future3 = listeningExecutorService.submit(new MyCallable(3000));

    ListenableFuture> future = Futures.successfulAsList(future1, future2, future3);

    // 增加回调
    FutureCallback> futureCallback = new FutureCallback>() {

        /**
         * 成功时调用
         * @param result
         */
        @Override
        public void onSuccess(List 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();
}

  控制台打印:

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
  • 测试返回结果同步转换
/**
 * 测试返回结果同步转换
 */
@Test
public void testTransform() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    ListenableFuture future1 = listeningExecutorService.submit(new MyCallable(1000));

    // 将返回结果转换为字符串
    ListenableFuture transform = Futures.transform(future1, new Function() {

        @Override
        public String apply(Long input) {
            String result = String.valueOf(input);
            log.info("将Long[" + input + "] 转换为 String[" + result + "]");
            return "String -> " + result;
        }

    }, listeningExecutorService);

    // 增加回调
    FutureCallback futureCallback = new FutureCallback() {

        /**
         * 成功时调用
         * @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();
}

  控制台打印:

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
  • 测试返回结果异步转换
/**
 * 测试返回结果异步转换
 */
@Test
public void testTransformAsync() throws InterruptedException {
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

    ListenableFuture future1 = listeningExecutorService.submit(new MyCallable(1000));

    // 将返回结果转换为字符串
    ListenableFuture transform = Futures.transformAsync(future1, new AsyncFunction() {

        @Override
        public ListenableFuture apply(Long input) throws Exception {
            String result = String.valueOf(input);
            log.info("将Long[" + input + "] 转换为 String[" + result + "]");
            return Futures.immediateFuture("String -> " + result);
        }

    }, listeningExecutorService);

    // 增加回调
    FutureCallback futureCallback = new FutureCallback() {

        /**
         * 成功时调用
         * @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();
}

  控制台打印:

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

你可能感兴趣的:(面试,guava,java,职场和发展,开发语言)