Guava——ListenableFuture

缘由

To simplify matters, Guava extends the Future interface of the JDK with ListenableFuture
We strongly advise that you always use ListenableFuture instead of Future in all of your code, because:

  • Most Futures methods require it.
  • It's easier than changing to ListenableFuture later.
  • Providers of utility methods won't need to provide Future and ListenableFuture variants of their methods.

Interface

A ListenableFuture allows you to register callbacks to be executed once the computation is complete, or if the computation is already complete, immediately. This simple addition makes it possible to efficiently support many operations that the basic Future interface cannot support.

The basic operation added by ListenableFuture is addListener(Runnable, Executor), which specifies that when the computation represented by this Future is done, the specified Runnable will be run on the specified Executor.

Adding Callbacks

Most users will prefer to use Futures.addCallback(ListenableFuture, FutureCallback, Executor), or the version which defaults to using MoreExecutors.directExecutor(), for use when the callback is fast and lightweight. A FutureCallback implements two methods:

  • onSuccess(V), the action to perform if the future succeeds, based on its result
  • onFailure(Throwable), the action to perform if the future fails, based on the failure

Creation

Corresponding to the JDK ExecutorService.submit(Callable) approach to initiating an asynchronous computation, Guava provides the ListeningExecutorService interface, which returns a ListenableFuture wherever ExecutorService would return a normal Future. To convert an ExecutorService to a ListeningExecutorService, just useMoreExecutors.listeningDecorator(ExecutorService).

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture explosion = service.submit(new Callable() {
  public Explosion call() {
    return pushBigRedButton();
  }
});
Futures.addCallback(explosion, new FutureCallback() {
  // we want this handler to run immediately after we push the big red button!
  public void onSuccess(Explosion explosion) {
    walkAwayFrom(explosion);
  }
  public void onFailure(Throwable thrown) {
    battleArchNemesis(); // escaped the explosion!
  }
});

Alternatively, if you're converting from an API based on FutureTask, Guava offers ListenableFutureTask.create(Callable) and ListenableFutureTask.create(Runnable, V). Unlike the JDK, ListenableFutureTask is not meant to be extended directly.

If you prefer an abstraction in which you set the value of the future rather than implementing a method to compute the value, consider extending AbstractFuture or using SettableFuture directly.

If you must convert a Future provided by another API to an ListenableFuture, you may have no choice but to use the heavyweight JdkFutureAdapters.listenInPoolThread(Future) to convert a Future to a ListenableFuture.
Whenever possible, it is preferred to modify the original code to return a ListenableFuture.

Application

The most important reason to use ListenableFuture is that it becomes possible to have complex chains of asynchronous operations.

* An AsyncFunction provides one method, ListenableFuture apply(A input). It can be used to asynchronously transform a value.

ListenableFuture rowKeyFuture = indexService.lookUp(query);
AsyncFunction queryFunction =
  new AsyncFunction() {
    public ListenableFuture apply(RowKey rowKey) {
      return dataService.read(rowKey);
    }
  };
ListenableFuture queryFuture =
    Futures.transformAsync(rowKeyFuture, queryFunction, queryExecutor);

Many other operations can be supported efficiently with a ListenableFuture that cannot be supported with a Future alone. Different operations may be executed by different executors, and a single ListenableFuture can have multiple actions waiting upon it.

When several operations should begin as soon as another operation starts -- "fan-out" -- ListenableFuture just works: it triggers all of the requested callbacks. With slightly more work, we can "fan-in," or trigger a ListenableFuture to get computed as soon as several other futures have all finished: see the implementation of Futures.allAsList for an example.

Avoid nested Futures

In cases where code calls a generic interface and returns a Future, it's possible to end up with nested Futures. For example:

executorService.submit(new Callable() {
  @Override
  public ListenableFuture call() {
    return otherExecutorService.submit(otherCallable);
  }
});

would return a ListenableFuture>.

This code is incorrect, because if a cancel on the outer future races with the completion of the outer future, that cancellation will not be propagated to the inner future.
It's also a common error to check for failure of the other future using get() or a listener, but unless special care is taken an exception thrown fromotherCallable would be suppressed.
To avoid this, all of Guava's future-handling methods (and some from the JDK) have Async versions that safely unwrap this nesting - transform(ListenableFuture
, Function, Executor) and transformAsync(ListenableFuture, AsyncFunction, Executor), or ExecutorService.submit(Callable) and submitAsync(AsyncCallable, Executor), etc.

CheckedFuture

Guava also provides a CheckedFuture interface. A CheckedFutureis a ListenableFuture that includes versions of the get methods that can throw a checked exception. This makes it easier to create a future that executes logic which can throw an exception. To convert a ListenableFuture to a CheckedFuture, useFutures.makeChecked(ListenableFuture, Function).

main class

  • MoreExecutors -- Executors
    该类是final类型的工具类,提供了很多静态方法。例如listeningDecorator方法初始化ListeningExecutorService方法,使用此实例submit方法即可初始化ListenableFuture对象。
  • ListeningExecutorService -- ExecutorService
    该类是对ExecutorService的扩展,重写ExecutorService类中的submit方法,返回ListenableFuture对象。
  • ListenableFuture -- Future
    该接口扩展了Future接口,增加了addListener方法,该方法在给定的excutor上注册一个监听器,当计算完成时会马上调用该监听器。不能够确保监听器执行的顺序,但可以在计算完成时确保马上被调用。
  • FutureCallback jdk没有的东西
    该接口提供了OnSuccess和OnFailuren方法。获取异步计算的结果并回调。
  • Futures
    该类提供和很多实用的静态方法以供使用。
  • ListenableFutureTask -- ListenableFutureTask
    该类扩展了FutureTask类并实现ListenableFuture接口,增加了addListener方法。

Future局限性

Future 具有局限性。在实际应用中,当需要下载大量图片或视频时,可以使用多线程去下载,提交任务下载后,可以从多个Future中获取下载结果,由于Future获取任务结果是阻塞的,所以将会依次调用Future.get()方法,这样的效率会很低。很可能第一个下载速度很慢,则会拖累整个下载速度。
Future主要功能在于获取任务执行结果和对异步任务的控制。但如果要获取批量任务的执行结果,从上面的例子我们已经可以看到,单使用 Future 是很不方便的

  • 没有好的方法去判断第一个完成的任务(可以用 CompletionService 解决,CompletionService 提供了一个 take() 阻塞方法,用以依次获取所有已完成的任务。)
  • Future的get方法 是阻塞的,使用不当会造成线程的浪费。(可以用 Google Guava 库所提供的 ListeningExecutorService 和 ListenableFuture 来解决)
  • 不能防止任务的重复提交。(要做到这件事就需要 Future 最常见的一个实现类 FutureTask 了)

在实际的使用中建议使用Guava ListenableFuture来实现异步非阻塞,目的就是多任务异步执行,通过回调的方方式来获取执行结果而不需轮询任务状态。

Test Code

使用callback

public static void testRateLimiter() {
        ListeningExecutorService executorService = MoreExecutors
                .listeningDecorator(Executors.newCachedThreadPool());

        RateLimiter limiter = RateLimiter.create(5.0); // 每秒不超过4个任务被提交
        List> listfutures = Lists.newArrayList();
        ListenableFuture tmp = null;
        for (int i = 0; i < 10; i++) {
            limiter.acquire(); // 请求RateLimiter, 超过permits会被阻塞
            tmp = executorService.submit(new Task(i));
            tmp.addListener(new Runnable() {
                @Override
                public void run() {
                    System.out.println("add Listener");
                }
            }, executorService);

            Futures.addCallback(tmp, new FutureCallback() {
                @Override
                public void onSuccess(Integer result) {
                    System.out.println("suc"+result);
                }

                @Override
                public void onFailure(Throwable t) {
                    System.out.println("fail"+t.toString());
                }
            });

            listfutures.add(tmp);

        }

        listfutures.forEach(e-> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("e = " + e.get());
                System.out.println("e = " + e.get());
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            } catch (ExecutionException e1) {
                e1.printStackTrace();
            }
        });
    }

    static class Task implements Callable {
        private int number;
        public Task(int i){
            this.number = i;
        }
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("call execute.." + number);
            return number;
        }
    }

使用链式future

那如果需要多重回调呢?

方法 描述
transform 加一个回调函数
allAsList 返回一个ListenableFuture ,该ListenableFuture 返回的result是一个List,List中的值是每个ListenableFuture的返回值,假如传入的其中之一fails或者cancel,这个Future fails 或者canceled
successAsList 返回一个ListenableFuture ,该Future的结果包含所有成功的Future,按照原来的顺序,当其中之一Failed或者cancel,则用null替代
public static void testLinkedFutureLisener() {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        final ListeningExecutorService poolService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4));
        ListenableFuture futureBase = poolService.submit(new Task("task1"));
        Futures.addCallback(futureBase, new FutureCallback() {
            @Override
            public void onSuccess(String result) {
                System.out.println("onSuccess result = " + result);
            }

            @Override
            public void onFailure(Throwable t) {
                System.out.println("onFailure result = " + t.toString());

            }
        });

        // 链式1

        ListenableFuture base_1 = Futures.transform(futureBase, new AsyncFunction() {
            public ListenableFuture apply(final String input) throws Exception {
                ListenableFuture temp = poolService.submit(new Callable() {
                    public String call() throws Exception {
                        System.out.println("base_1回调线程正在执行...input:"+input);
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("base_1回调线程 done");

                        return input + " & base_1回调线程的结果 ";
                    }
                });
                return temp;
            }
        }, poolService);

        ListenableFuture base_2 = Futures.transform(futureBase, new AsyncFunction() {
            public ListenableFuture apply(final String input) throws Exception {
                ListenableFuture temp = poolService.submit(new Callable() {
                    public String call() throws Exception {
                        System.out.println("base_2回调线程正在执行...input:"+input);
                        try {
                            Thread.sleep(2000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("base_2回调线程 done");

                        return input + " & base_2回调线程的结果 ";
                    }
                });
                return temp;
            }
        }, poolService);

        ListenableFuture first = Futures.transform(base_2, new AsyncFunction() {
            public ListenableFuture apply(final String input) throws Exception {
                ListenableFuture temp = poolService.submit(new Callable() {
                    public String call() throws Exception {
                        System.out.println("first回调线程正在执行...input:"+input);
                        try {
                            String resBase1 =  base_1.get();
                            System.out.println("resBase1 = " + resBase1);
                            countDownLatch.countDown();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("first 回调线程 done");

                        return input + " & first回调线程的结果 ";
                    }
                });
                return temp;
            }
        }, poolService);

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        poolService.shutdown();
    }

// 运行结果:
task1 doing
task1done
onSuccess result = task1
base_2回调线程正在执行...input:task1
base_1回调线程正在执行...input:task1
base_1回调线程 done
base_2回调线程 done
first回调线程正在执行...input:task1 & base_2回调线程的结果 
resBase1 = task1 & base_1回调线程的结果 
first 回调线程 done

Ref:
https://github.com/google/guava/wiki/ListenableFutureExplained
https://blog.csdn.net/pistolove/article/details/51232004

你可能感兴趣的:(Guava——ListenableFuture)