• 并发编程(三)Promise, Future 和 Callback


    并发编程(三)Promise, Future 和 Callback

    异步操作的有两个经典接口:Future 和 Promise,其中的 Future 表示一个可能还没有实际完成的异步任务的结果,针对这个结果可以添加 Callback 以便在任务执行成功或失败后做出对应的操作,而 Promise 交由任务执行者,任务执行者通过 Promise 可以标记任务完成或者失败。 可以说这一套模型是很多异步非阻塞架构的基础。

    这一套经典的模型在 Scala、C# 中得到了原生的支持,但 JDK 中暂时还只有无 Callback 的 Future 出现,当然也并非在 Java 界就没有发展了,比如 Guava 就提供了 ListenableFuture 接口,而 Netty 4+ 更是提供了完整的 Promise、Future 和 Listener 机制。

    一、Future 模式 - 将来式(JDK)

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Future<Integer> future = executorService.submit(() -> {
        TimeUnit.SECONDS.sleep(5);
        return 5;
    });
    Integer result = future.get();
    

    二、Future 模式--回调式(Guava)

    Future 模式的第二种用法便是回调。很不幸的事,JDK 实现的 Future 并没有实现 callback, addListener 这样的方法,想要在 JAVA 中体验到 callback 的特性,得引入一些额外的框架。

    <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
       <version>21.0</version>
    </dependency>
    
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
    ListenableFuture<Integer> future = service.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(5);
            return 100;
        }
    });
    Futures.addCallback(future, new FutureCallback<Integer>() {
        public void onSuccess(Integer result) {
            System.out.println("success:" + result);
        }
    
        public void onFailure(Throwable throwable) {
            System.out.println("fail, e = " + throwable);
        }
    });
    
    Thread.currentThread().join();
    

    三、Future 模式--回调式(Netty4)

    Netty 除了是一个高性能的网络通信框架之外,还对 jdk 的Future 做了扩展,引入 Netty 的 maven 依赖

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.22.Final</version>
    </dependency>
    
    EventExecutorGroup group = new DefaultEventExecutorGroup(4); // 4 threads
    io.netty.util.concurrent.Future<Integer> f = group.submit(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(5);
            return 100;
        }
    });
    f.addListener(new FutureListener<Object>() {
        @Override
        public void operationComplete(io.netty.util.concurrent.Future<Object> objectFuture) throws Exception {
            System.out.println("计算结果::"+objectFuture.get());
        }
    });
    

    四、由 Callback Hell 引出 Promise 模式

    同样的如果你对 ES6 有所接触,就不会对 Promise 这个模式感到陌生,如果你对前端不熟悉,也不要紧,我们先来看看回调地狱(Callback Hell)是个什么概念。

    回调是一种我们推崇的异步调用方式,但也会遇到问题,也就是回调的嵌套。当需要多个异步回调一起书写时,就会出现下面的代码(以 js 为例):

    asyncFunc1(opt, (...args1) => {
       asyncFunc2(opt, (...args2) => {
           asyncFunc3(opt, (...args3) => {
                asyncFunc4(opt, (...args4) => {
                    // some operation
                });
            });
        });
    });
    

    虽然在 Java 业务代码中很少出现回调的多层嵌套,这样的代码不易读,嵌套太深修改也麻烦。于是 ES6 提出了 Promise 模式来解决回调地狱的问题。可能就会有人想问:Java 中存在 Promise 模式吗?答案是肯定的。

    前面提到了 Netty 和 Guava 的扩展都提供了 addListener 这样的接口,用于处理 Callback 调用,但其实 jdk1.8 已经提供了一种更为高级的回调方式:CompletableFuture。首先尝试用 CompletableFuture 来解决回调的问题。

    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
        TimeUnit.SECONDS.sleep(5);
        return 100;
    });
    completableFuture.whenComplete((result, e) -> {
        System.out.println("结果:" + result);
    });
    Thread.currentThread().join();
    

    五、Future 和 Promise

    Netty 文档说明 Netty 的网络操作都是异步的, 在源码上大量使用了 Future/Promise 模型,在 Netty 里面也是这样定义的:

    • Future 接口定义了 isSuccess(), isCancellable(), cause() 这些判断异步执行状态的方法。(read-only)
    • Promise 接口在 extends Future 的基础上增加了 setSuccess(), setFailure() 来标记任务完成或者失败。(writable)

    Future

    JDK 的 Future

    public interface Future<V> {
        // 取消异步操作
        boolean cancel(boolean mayInterruptIfRunning);
        // 异步操作是否取消
        boolean isCancelled();
        // 异步操作是否完成,正常终止、异常、取消都是完成
        boolean isDone();
    
        // 阻塞直到取得异步操作结果
        V get() throws InterruptedException, ExecutionException;
        // 同上,但最长阻塞时间为timeout
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    

    Netty 对 JDK 的 Future 进行了扩展

    public interface Future<V> extends java.util.concurrent.Future<V> {
        // 异步操作完成且正常终止
        boolean isSuccess();
        // 异步操作是否可以取消
        boolean isCancellable();
        // 异步操作失败的原因
        Throwable cause();
        // 添加一个监听者,异步操作完成时回调,类比javascript的回调函数
        Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
        Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
        // 阻塞直到异步操作完成
        Future<V> await() throws InterruptedException;
        // 同上,但异步操作失败时抛出异常
        Future<V> sync() throws InterruptedException;
        // 非阻塞地返回异步结果,如果尚未完成返回null
        V getNow();
    }
    

    Netty 的 Promise 对又对 Future 进行了扩展

    public interface Promise<V> extends Future<V> {    
        Promise<V> setSuccess(V result);
        boolean trySuccess(V result);
        Promise<V> setFailure(Throwable cause);
        boolean tryFailure(Throwable cause);
        boolean setUncancellable();
    }
    

    DefaultChannelPromise 是 ChannelPromise 的实现类,它是实际运行时的 Promoise 实例。

    参考:

    1. 《并发编程 Promise, Future 和 Callback》:https://ifeve.com/promise-future-callback/

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    MySQL报错【innoDB is limited to rowlogging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED】
    【转】linux下解压命令大全
    java学习笔记
    【转】Linux安装MySQL tar文件
    Android部署及安装
    正则表达式各个符合含义
    [转]Linux下crontab命令的用法
    XTABLE学习笔记
    MySQL的mysqldump工具的基本用法
    java开发注意事项
  • 原文地址:https://www.cnblogs.com/binarylei/p/10024266.html
Copyright © 2020-2023  润新知