Future模式与异步回调模式

写再前面

Future模式与异步回调模式二者十分相似又存在区别,所以将两个和在一起进行总结。

Future模式

什么是Future模式

长篇大论

用生活中的例子来打个比喻,就像叫外卖。比如在午休之前我们可以提前叫外卖,只需要点好食物,下个单。然后我们可以继续工作。到了中午下班的时候外卖也就到了,然后就可以吃个午餐,再美滋滋的睡个午觉。而如果你在下班的时候才叫外卖,那就只能坐在那里干等着外卖小哥,最后拿到外卖吃完午饭,午休时间也差不多结束了。

Future 模式是高并发设计与开发过程中常见的设计模式,它的核心思想是异步调用。对于 Future 模式来说,它不是立即返回你需要的数据,但是它会返回一个契约(或者说异步任务),将来你可以凭借这个契约(或异步任务)去获取你需要的结果。

在进行传统的 RPC(远程调用)时,同步调用 RPC 是一段耗时的过程。当客户端发出 RPC请求,服务端完成请求处理需要很长的一段时间才会返回,这个过程中客户端一直在等待,直到 数据返回随后再进行其他任务的处理。现有一个 Client 同步对三个 Server 分别进行一次 RPC 调 用。
image.png
假设一次远程调用的时间为 500ms,则一个 Client 同步对三个 Server 分别进行一次 RPC 调 用的总时间,需要耗费 1500ms。如果节省这个总时间呢,可以使用 Future 模式对其进行改造,将同步的 RPC 调用改为异步并发的 RPC 调用,一个 Client 异步并发对三个 Server 分别进行一次 RPC 调用
image.png

假设一次远程调用的时间为 500ms,则一个 Client 异步并发对三个 Server 分别进行一次 RPC调用的总时间,还只要耗费 500ms。使用 Future 模式异步并发地进行 RPC 调用,客户端在得到一 个 RPC 的返回结果前,并不急于获取该结果,而是充分利用等待时间去执行其他的耗时操作(如其他 RPC 调用)这就是 Future 模式的核心所在。
Future 模式的核心思想是异步调用,有点类似于异步的 Ajax 请求。当调用某个耗时方法时, 可以不急于立刻获取结果,可以让被调用者立刻返回一个契约(或异步任务), 并且将耗时的方法放到另外线程执行,后续凭契约再去获取异步执行的结果。
在具体的实现上,Future 模式和异步回调模式既有区别,又有联系。Java 的 Future 模式实现,没有实现异步回调模式,仍然需要主动去获取耗时任务的结果;而 Java 8 中的 CompletableFuture 组件,实现了异步回调模式。

一句话总结

使用Future模式,获取数据的时候无法立即得到需要的数据。而是先拿到一个契约,你可以再将来需要的时候再用这个契约去获取需要的数据,这个契约就好比叫外卖的例子里的外卖订单。
Futute模式核心在于去除了主调用函数的等待时间,并使得原本需要等待的时间可以充分利用来处理其他业务逻辑,充分的利用了系统资源。

简单版本的实现

我通过上面的点外卖的故事来实现一个简单的Future模式,这样更利于大家领会其中的奥妙。

首先是FutureData,它是只是一个包装类,创建它不需要耗时。在工作线程准备好数据之后可以使用setData方法将数据传入。而客户端线程只需要在需要的时候调用getData方法即可,如果这个时候数据还没有准备好,那么getData方法就会等待,如果已经准备好了就好直接返回。

/*用于存储返回结果*/
public class FutureData<T> {
    /*标志位用于判断数据是否已经存储完成*/
    private boolean mIsReady=false;
    private T mData;
    public synchronized void setData(T data){
        mIsReady=true;
        mData=data;
        /*唤醒操作*/
        notifyAll();
    }
    public synchronized T getData(){
        /*wait:
        * 让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。
        * “直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,
        * 当前线程被唤醒(进入“就绪状态”)
        * */
        if (!mIsReady){
            try {
                /*如果没有则数据准备好则进入等待状态,等待唤醒*/
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return mData;
    }
}

接着是服务端,客户端在向服务端请求数据的时候服务端不会实际去加载数据,它只是创建一个FutureData,然后创建子线程去加载,而它只需要直接返回FutureData就可以了。

public class Server {
    public FutureData<String> getData(){
        final FutureData<String> data = new FutureData<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    /*睡眠1000毫秒,模拟RPC远程调用的执行时间*/
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*向FutureData中加入数据*/
                data.setData("炸鸡到了!!");
            }
        }).start();
        return data;
    }
}

客户端代码如下。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Server server = new Server();
        /*返回一个Future,后续通过Future对象去取实际的内容*/
        System.out.println("点个炸鸡吃吃!!");
        FutureData<String> future = server.getData();
        System.out.println("炸鸡没到,先处理其他事情。。。");
        /*睡眠1000秒模拟处理其他事情*/
        Thread.sleep(1000);
        System.out.println("其他事情处理好了!!去看看炸鸡到没!!");
        /*获取Future中的数据,如果准备好了,则直接获取,如果没有则阻塞*/
        String data = future.getData();
        System.out.println("炸鸡到了!!");
    }
}

从上述的案例我们可以看到节省的时间为等待外卖的过程中我们又同时处理了其他的事情。
JDK中的实现版本肯定不会像我这般简陋,下面我们来学习学习JDK中是如何实现的。

JDK中的实现

Runable接口

在多线程的编程中我们经常使用到Runable接口,Runable接口有如下众多优点。

**灵活:**Runnable可以继承其他类实现对Runnable实现类的增强,避免了Thread类由于继承Thread类而无法继承其他类的问题。
**共享资源: **Runnable接口的run()方法可以被多个线程共享,适用于多个进程处理一种资源的问题。

**缺点: **但是Runable接口有一个巨大的缺点,就是他执行完任务不会返回结果,这在很多场景下是不适用的。

所以为了执行异步执行的结果问题,Java语言在1.5 版本之后提供了一种的新的多线程创建方法: 通过 Callable 接口和 FutureTask 类相结合创建线程。

Callable接口

Callable 接口位于 java.util.concurrent 包中,翻开 Java 源代码,Callable 的代码如下:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable 接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法 call()有返回 值,返回值的类型为 Callable 接口的泛型形参类型;call 抽象方法还有一个 Exception 的异常声明,容许方法的实现版本内部的异常直接抛出,并且可以不予捕获(子父类之间异常处理的关系)。

子类声明异常不能超出父类的范围

[1]父类没有声明异常,子类也不能

[2]不可抛出原有方法抛出异常类的父类或上层类

[3]抛出的异常类型的数目不可以比原有的方法抛出的还多(不是指个数)

[4]如果程序有多个catch,应该子类异常在前,父类异常在后。

Callable 接口类似于 Runnable。不同的是,Runnable 的唯一抽象方法 run()没有返回值,也 没有受检异常的异常声明。比较而言,Callable 接口的 call()有返回值有返回值,并且申明了受 检异常,其功能更强大一些。

问题:Callable 实例能否和 Runnable 实例一样,作为 Thread 线程实例的 target 来使用吗?答 案是不行:Thread 的 target 属性的类型为 Runnable,而 Callable 接口与Runnable 接口之间没有任 何的继承关系,并且二者唯一方法在的名字上也不同。显而易见,Callable 接口实例没有办法作为 Thread 线程实例的 target 来使用。既然如此,那么该如何使用 Callable 接口去创建线程呢?一个 重要的在 Callable 接口与 Thread 线程之间起到搭桥作用的接口,马上就要登场了。

RunableFuture接口

这个重要中间搭桥接口,就是 RunnableFuture 接口,该接口与 Runnable 接口、Thread 类紧密 相关的。与 Callable 接口一样,RunnableFuture 接口也是位于 java.util.concurrent 包,使用的时候需要的 import 导入。
RunnableFuture 是如何在 Callable 与 Thread 中间实现搭桥功能的呢?RunnableFuture 接口实现了两个目标:一是可以作为 Thread 线程实例的 target 实例;二是可以获取异步执行的结果。它 是如何做到一箭双雕的呢?请看 RunnableFuture 的接口的代码:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通过源码可以看出,RunnableFuture 继承 Runnable 接口,从而保证了其实例可以作为 Thread线程实例的 target 目标;同时,RunnableFuture 通过继承 Future 接口,从而保证了通过它可以获 取未来的异步执行结果。
在这里,一个新的、从来没有介绍过的、又非常重要的 Future 接口,马上登场。

Future接口

需要实现的基本功能:

(1)能够取消异步执行中的任务。
(2)判断异步任务是否执行完成。
(3)获取异步任务完成后的执行结果。

源码:

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);
   
    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;
    
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

  1. V get():获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果 异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到到异步任务执行完成,其异步结果返回给调用线程。
  2. V get(Long timeout , TimeUnit unit) :设置时限,(调用线程)阻塞性的获取异 步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一 个阻塞时长限制,不会无限制的阻塞和等待,如果其阻塞时间超过设定的 timeout 时间,该方法将抛出异常,调用线程可捕获此异常。
  3. boolean isDone():获取异步任务的执行状态。如果任务执行结束,返回 true。
  4. boolean isCancelled():获取异步任务的取消状态。如果任务完成前被取消,则 返回 true。
  5. boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。

总体来说,Future 是一个对异步任务进行交互、操作的接口。但是 Future 仅仅是一个接口, 通过它没有办法直接完成对异步任务的操作,JDK 提供了一个默认的实现了——FutureTask 类。

FutureTask类

FutureTask 类是 Future 接口的实现类,提供对异步任务的操作的具体实现。但是,FutureTask 类不仅仅实现了 Future 接口,而且实现了 Runnable 接口,或者更加准确地说,FutureTask 类实现 了 RunnableFuture 接口。
前面讲到 RunnableFuture 接口很关键,既可以作为 Thread 线程实例的 target 目标,也可以获 取并发任务执行的结果,是 Thread 与 Callable 之间一个非常重要的搭桥角色。但是,RunnableFuture 只是一个接口,无法直接创建对象,如果需要创建对象,就需用到它的实现类——FutureTask 类。
所以说,FutureTask 类才是真正的、最终的 Thread 与 Callable 之间的搭桥类。
从 FutureTask 类的 UML 关系图可以看到:FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 接口继承了 Runnable 接口和 Future 接口,所以,FutureTask 既能当做一个 Runnable 类型的 target 执行目标直接被 Thread 执行,也能作为 Future 异步任务来获取 Callable 的计算结 果。
FutureTask 如何完成多线程的并发执行、任务结果的异步获取的呢?FutureTask 内部有一个 Callable 类型的成员——callable 实例属性,具体如下:

private Callable<V> callable;

callable 实例属性用来保存并发执行的 Callable类型的任务,并且,callable 实例属性需要 在 FutureTask 实例构造时进行初始化。FutureTask 类实现了 Runnable 接口,在其 run()方法的实现 版本中,会执行 callable 成员的 call()方法。

此外,FutureTask 内部还有另一个非常重要的 Object 类型的成员——outcome 实例属性:

 private Object outcome;

**FutureTask的outcome实例属性用于保存callable成员call()方法的异步执行结果。**在FutureTask 类 run()方法完成 callable 成员的 call()方法的执行之后,其结果将被保存在 outcome 实例属性中, 供 FutureTask 类的 get()方法去获取。

总体继承关系图

image.png

使用Callable和FutureTask创建线程的具体步骤

仍然以点外卖的的背景来实现这个案例,等待外卖派送的过程就相当于RPC远程调用等待的过程!!

基本步骤:

(1)创建一个 Callable 接口的实现类,并实现其 call()方法,编写好异步执行的具体逻辑, 并且可以有返回值。
(2)使用 Callable 实现类的实例,构造一个 FutureTask 实例。
(3)使用 FutureTask 实例,作为 Thread 构造器的 target 入参,构造新的 Thread 线程实例;
(4)调用 Thread 实例的 start 方法启动新线程,启动新线程的 run()方法并发执行。其内部的 执行过程为:启动 Thread 实例的 run()方法并发执行后,会执行 FutureTask 实例的 run()方法,最 终会并发执 Callable 实现类的 call()方法。
(5)调用 FutureTask 对象的 get()方法,阻塞性的获得并发线程的执行结果。

public class CallableUse {
    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        /*实现一个Callable接口*/
        Callable<String> callableImp = new Callable<String>() {
            //编写好异步执行的具体逻辑,并且可以有返回值
            @Override
            public String call() throws Exception {
                System.out.println("订单制作派送中。。。");
                Thread.sleep(1050);
                return "外卖到了!!";
            }
        };
        /*创建一个futureTask类*/
        FutureTask<String> futureTask = new FutureTask<>(callableImp);
        /*创建一个线程进行执行*/
        System.out.println("肚子饿了,点个外卖!!");
        new Thread(futureTask,"returnableThread").start();
        /*主线程模拟干其他事情*/
        System.out.println("外卖还没到,干点其他事情");
        Thread.sleep(1000);
        System.out.println("任务干完了,看看外卖到没!!");
        /*这是一个阻塞操作*/
        String s = futureTask.get();
        System.out.println(s);
    }
}

执行结果:

肚子饿了,点个外卖!!
外卖还没到,干点其他事情
订单制作派送中。。。
任务干完了,看看外卖到没!!
外卖到了!!

流程分析:

在这个例子中有两个线程:一个是执行 main 方法的主线程,名字叫做“main”;另一个是 “main”线程通过 thread.start 方法启动的业务线程,叫做“returnableThread”线程。该线程是一 个包含了 FutureTask 任务作为 target 的 Thread 线程。
“main”线程通过 thread.start()启动“returnableThread”线程之后,“main”线程会继续自己的事情。“returnableThread”线程开始并发执行。
“returnableThread”线程首先开始执行的,是 thread.run 方法,然后在其中会执行到其 target (futureTask 任务)的 run 方法;接着在这个 futureTask.run 方法中,会执行 futureTask 的 callable 成员的 call 方法,在这里的 callable 成员(ReturnableTask 实例)是通过 FutureTask 构造器在初始 化时传递进来的、自定义的 Callable 实现类的实例。
“main”线程和“returnableThread”线程的执行流程,大致如图 1-9 所示。
image.png

FutureTask 的 Callable 成员的 call()方法执行完成后,会将结果保存在 FutureTask 内部的 outcome 实例属性中。以上演示实例的 Callable 实现类中,这里 call()方法中业务逻辑的返回结果,是 "外卖到了!!"这句话。

"外卖到了!!"这句话被返回之后,作为结果将被保存在 FutureTask 内部的 outcome 实例属性中,至此, 异步的“returnableThread”线程执行完毕。在“main”线程处理完自己的事情(以上实例中是一 个消磨时间的循环)后,通过 futureTask 的 get 实例方法获取异步执行的结果。这里有两种情况:
(1)futureTask 的结果 outcome 不为空,callable.call()执行完成;在这种情况下,futureTast.get 会直接取回 outcome 结果,返回给“main”线程(结果获取线程)。
(2)futureTask 的结果 outcome 为空,callable.call()还没有执行完。
在这种情况下,“main”线程作为结果获取线程会被阻塞住,一直被阻塞到 callable.call()执行 完成。当执行完后,最终结果保存到 outcome 中,futureTask 会唤醒的“main”线程,去提取callable.call()执行结果。

异步回调模式

FutureTask的缺点

通过 FutureTask 的 get 方法获取异步结果时,主线程也会被阻塞的。是异步阻塞模式。异步阻塞的效率往往是比较低的,被阻塞的主线程,不能干任何事情,唯一能干的,就是在 傻傻等待。原生 Java API,除了阻塞模式的获取结果外,并没有实现非阻塞的异步结果获取方法。
如果需要用到获取异步的结果,得引入一些额外的框架,这里首先介绍谷歌的 Guava 框架。

异步调用与主动调用

主动调用:

主动调用是一种阻塞式调用,它是一种单向调用,“调用方”要等待“被调用方”执行完毕 才返回。如果“被调用方”的执行的时间很长,那么“调用方”线程需要阻塞很长一段时间。就像FutureTask的get方法。

异步调用:

在回调模式中负责执行回调方法的具体线程已经不再是调用方线程(而是变成了异步的被调用方线程(如烧水线程)。
Java 中回调模式的标准实现类为 CompletableFuture,由于该类出现的时间比较晚,所以很 多的著名的中间件如 Guava、Netty 等都提供了自己的异步回调模式 API 供开发者们使用。开发 者还可以使用 RxJava 响应式编程组件进行异步回调的开发。

Guava 的异步回调模式

Guava 是 Google 提供的 Java 扩展包,它提供了一种异步回调的解决方案。Guava 中与异步 回调相关的源码,处于 com.google.common.util.concurrent 包中。包中的很多类都是对 java.util.concurrent 能力扩展和能力增强。比如,Guava 的异步任务接口 ListenableFuture 扩展了 Java 的 Future 接口,实现了异步回调的的能力。

FutureCallback

总体来说,Guava 的主要增强了 Java 而不是另起炉灶。为了实现异步回调方式获取异步线程 的结果,Guava 做了以下的增强:

  • 引入得了一个新的接口 ListenableFuture,继承了 Java 的 Future 接口,使得 Java 的 Future 异步任务,在 Guava 中能被监控和非阻塞获取异步结果。
  • 引入了一个新的接口 FutureCallback,这是一个独立的新接口。该接口的目的是在异步任务执行完成后,根据异步结果完成不同的回调处理,并且可以处理异步结果。

FutureCallback 是一个新增的接口,用来填写异步任务执行完后的监听逻辑。FutureCallback拥有两个回调方法:

  • onSuccess 方法,在异步任务执行成功后被回调;调用时,异步任务的执行结果,作为onSuccess 方法的参数被传入。
  • onFailure 方法,在异步任务执行过程中,抛出异常时被回调;调用时,异步任务所抛出的 异常,作为onFailure 方法的参数,被传入。
@GwtCompatible
public interface FutureCallback<V> {
  /**
   * Invoked with the result of the {@code Future} computation when it is successful.
   */
  void onSuccess(@Nullable V result);

  /**
   * Invoked when a {@code Future} computation fails or is canceled.
   *
   * <p>If the future's {@link Future#get() get} method throws an {@link ExecutionException}, then
   * the cause is passed to this method. Any other thrown object is passed unaltered.
   */
  void onFailure(Throwable t);
}

详解 ListenableFuture

image.png

看 ListenableFuture 接口名称,知道它与 Java 中 Future 接口的亲戚关系。没错,Guava 的ListenableFuture 接口是对 Java 的 Future 接口的扩展,可以理解为异步任务实例。源码如下:

@GwtCompatible
public interface ListenableFuture<V> extends Future<V> {
  void addListener(Runnable listener, Executor executor);
}

ListenableFuture 仅仅增加了一个 addListener 方法。它的作用就是将前一小节的 FutureCallback善后回调逻辑,封装成一个内部的 Runnable 异步回调任务,在 Callable 异步任务完成后,回调 FutureCallback 善后逻辑。

注意,此 addListener 方法只在 Guava 内部使用,如果对它感兴趣,可以查看 Guava 源码。在实际编程中,addListener 不会使用到。
在实际编程中,如何将 FutureCallback 回调逻辑绑定到异步的 ListenableFuture 任务呢?可以 使用 Guava 的 Futures 工具类,它有一个 addCallback 静态方法,可以将 FutureCallback 的回调实例绑定到 ListenableFuture 异步任务。下面是一个简单的绑定实例:

Futures.addCallback(listenableFuture, new FutureCallback<Boolean>()
 {
        public void onSuccess(Boolean r)
       {
        // listenableFuture 内部的 Callable 成功时候的回调此方法
        }
       public void onFailure(Throwable t)
        {
            // listenableFuture 内部的 Callable 异常时候的回调此方法
        }
        });

ListenableFuture 异步任务

如果要获取 Guava 的 ListenableFuture 异步任务实例,主要是通过向线程池(ThreadPool)提 交 Callable 任务的方式获取。不过,这里所说的线程池,不是 Java 的线程池,而是经过 Guava 自己的定制过的 Guava 线程池。
Guava 线程池是对 Java 线程池的一种装饰。创建 Guava 线程池的方法如下:

//java 线程池 
ExecutorService jPool = Executors.newFixedThreadPool(10); 
// Guava 线程池 
ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool); 

首先创建 Java 线程池,然后以其作为 Guava 线程池的参数,再构造一个 Guava 线程池。有 了 Guava 的线程池之后,就可以通过 submit 方法来提交任务了;任务提交之后的返回结果,就是 我们所要的 ListenableFuture 异步任务实例了。
简单来说:获取异步任务实例的方式,通过向线程池提交 Callable 业务逻辑来实现。代码如 下:

//submit 方法来提交任务,返回异步任务实例 
ListenableFuture<Boolean> hFuture = gPool.submit(hJob); 
//绑定回调实例 
Futures.addCallback(listenableFuture, new FutureCallback<Boolean>() 
{ 
 //有两种实现回调的方法 
}); 

取到了 ListenableFuture 实例后,通过 Futures.addCallback 方法,将 FutureCallback 回调逻辑的实例,绑定到 ListenableFuture 异步任务实例,实现异步执行完成后的回调。
总结一下,Guava 异步回调的流程如下:
**第一步:**实现 Java 的 Callable 接口,创建的异步执行逻辑。还有一种情况,如果不需要返回 值,异步执行逻辑也可以实现 Runnable 接口。
**第二步:**创建 Guava 线程池。
**第三步:**将第一步创建的 Callable/Runnable 异步执行逻辑的实例,submit 提交到 Guava 线程 池,从而获取 ListenableFuture 异步任务实例。
**第四步:**创建 FutureCallback 回调实例,通过 Futures.addCallback,将回调实例绑定到ListenableFuture 异步任务上。
完成以上四步,当 Callable/Runnable 异步执行逻辑完成后,就会回调异步回调实例FutureCallback 实例的回调方法 onSuccess/onFailure。

案例实现

这里需要将前面的外卖案例改造一下。改造成以下背景:
你女朋友饿了需要你给他点个外卖,你只需要给他点了就好了,因为你已经把他的电话号码地址等等都写在了订单上,商家在把外卖制作完成之后知道怎么把外卖送到你女朋友的手上你不需要再去关心任何事情。

package com.Test.Future.GuavaFutureDemo;

import com.google.common.util.concurrent.*;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: GuavaFutureDemo
 * @Author: 86151
 * @Date: 2021/11/5 16:15
 * @Description: TODO
 */
public class GuavaFutureDemo {
    static class callableImp implements Callable<String>{
        @Override
        public String call() throws Exception {
            System.out.println("开始制作外卖!!");
            /*模拟外卖制作消耗的时间*/
            Thread.sleep(1000);
            return "宫保鸡丁";
        }
    }

    static void eatFood(){
        System.out.println("女朋友收到外卖开始吃外卖!!");
    }

    public static void main(String[] args) {
        /*创建一个callable实现类*/
        callableImp callableImp = new callableImp();
        /*创建一个线程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        /*包装Java线程池,构造guava线程池*/
        ListeningExecutorService gPool = MoreExecutors.listeningDecorator(executorService);
        /*设置钩子函数*/
        FutureCallback<String> hook = new FutureCallback<String>() {
            /*在里面执行数据返回的业务逻辑*/
            @Override
            public void onSuccess(String s) {
                System.out.println(s+"到了...");
                /*执行的具体业务逻辑这里省略*/
                /*这里可以理解为将外卖送到后你女朋友开始吃外卖*/
                eatFood();
                /*相当于RPC远程调用返回数据后执行什么操作*/
            }

            /*执行错误的执行逻辑*/
            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("外卖丢了。。。");
                System.out.println("执行退款赔偿流程。。");
            }
        };
        /*开始点外卖*/
        System.out.println("开始点外卖!!");
        ListenableFuture<String> hotFuture = gPool.submit(callableImp);
        /*设置回调钩子*/
        Futures.addCallback(hotFuture,hook);
    }
}

Guava异步回调与Java异步调用的区别

(1)FutureTask 是主动调用的模式,“调用线程”主动获得异步结果;在获取异步结果时处 于阻塞状态,并且会一直阻塞,直到拿到异步线程的结果。
(2)Guava 是异步回调模式,“调用线程”不会主动去获得异步结果,而是准备好回调函数, 并设置好回调钩子;执行回调函数的并不是“调用线程”自身,回调函数的执行者,是“被调用 线程”;“调用线程”在执行完自己的业务逻辑后,就已经结束了;当回调函数被执行时,“调 用线程”可以已经结束很久了。相当于主函数先编写好了回调结果的处理逻辑让被调用线程去执行。

Netty异步回调模式

Netty 官方文档说明——Netty 的网络操作都是异步的。Netty 源码中,大量使用了异步回调 处理模式。在 Netty 的业务开发层面,处于 Netty 应用的 Handler 处理器中的业务处理代码,也都是异步执行的。所以,了解 Netty 的异步回调,无论是 Netty 应用开始还是源码级开发,都是十分重要的。
Netty 和 Guava 一样,实现了自己的异步回调体系:Netty 继承和扩展了 JDK Future 系列异步 回调的 API,定义了自身的 Future 系列接口和类,实现异步任务的监控、异步执行结果的获取。
**总体来说, Netty 对 Java Future 异步任务的 扩展如下: **
继承 Java 的 Future 接口得到一个新的属于 Netty 自己的 Future 异步任务接口;该接口对原有的接口进行了增强,使得 Netty 异步任务,能够非阻塞的处理回调结果;注意,Netty 没有修改 Future的名称,只是调整了所在的包名,Netty 的 Future 类的包名和 Java 的 Future 接口的包不同。
引入了一个新接口——GenericFutureListener,用于表示异步执行完成的监听器。这个接口和Guava 的 FutureCallbak 回调接口不同。Netty 使用了监听器的模式,异步任务的执行完成后的回 调逻辑,抽象成了 Listener 监听器接口。可以将 Netty 的 GenericFutureListener 监听器接口,加入Netty 异步任务 Future 中,实现对异步任务执行状态的事件监听。
总体来说,在异步非阻塞回调的设计思路上,Netty 和 Guava 的思路是一致的。对应关系为:
(**1)Netty 的 Future 接口,可以对应到 Guava 的 ListenableFuture 接口; **
(2)Netty 的 GenericFutureListener 接口,可以对应到 Guava 的 FutrueCallback 接口。

GenericFutureListener 接口详解

前面提到,和 Guava 的 FutrueCallback 一样,Netty 新增了一个接口,来封装异步非阻塞回调 的逻辑——它就是 GenericFutureListener 接口。
GenericFutureListener 位于 io.netty.util.concurrent 包中,源码如下

public interface GenericFutureListener<F extends Future<?>>
        extends EventListener {
    //监听器的回调方法
    void operationComplete(F var1) throws Exception;
}

GenericFutureListener 拥有一个回调方法:operationComplete,表示异步任务操作完成。在Future 异步任务执行完成后,将回调此方法。大多数情况下,Netty 的异步回调的代码,编写在 GenericFutureListener 接口的实现类中的 operationComplete 方法中。
说明下,GenericFutureListener 的父接口 EventListener,是一个空接口,没有任何的抽象方法, 是一个仅仅具有标识作用的接口。

NettyFuture接口详解

Netty 也对 Java 的 Future 接口的扩展,并且名称没有变,还是叫做 Future 接口,实现在 io.netty.util.concurrent 包中。
和 Guava 的 ListenableFuture 一样,Netty 的 Future 接口扩展了一系列的方法,对执行的过程 的进行监控,对异步回调完成事件进行 Listen 监听,并且回调。Netty 的 Future 的源码如下:

public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess(); // 判断异步执行是否成功
    boolean isCancellable(); // 判断异步执行是否取消
    Throwable cause();//获取异步任务异常的原因
    //增加异步任务执行完成 Listener 监听器
    Future<V> addListener(GenericFutureListener<? extends Future<? super
            V>> listener);
    //移除异步任务执行完成 Listener 监听器
    Future<V> removeListener(GenericFutureListener<? extends Future<? super
            V>> listener);
    //...
}

Netty 的 Future 接口一般不会直接使用,使用过程中会使用器子接口。Netty 有一系列的子接 口,代表不同类型的异步任务,如 ChannelFuture 接口。
ChannelFuture 子接口表示 Channel 通道 I/O 操作的异步任务;如果在 Channel 的异步 I/O 操作完成后,需要执行回调操作,就需要使用到 ChannelFuture 接口。

CompleteFuture异步回调

很多语言(如 JavaScript)提供了异步回调,一些 Java 中间件(如 Netty、Guava)也提供了 异步回调 API,为开发者带来更好的异步编程工具。Java8 提供一个新的、具备异步回调能力的工具类——CompletableFuture,该类实现了 Future 接口,并提供了异步回调的能力,还具备函数式 编程的能力。

CompleteableFuture详解

继承关系图

Future 接口大家已经非常熟悉了,接下来介绍一下 CompletionStage 接口。CompletionStage 代 表异步计算过程中的某一个阶段,一个阶段完成以后可能会进入另外一个阶段。一个阶段可以理解为一个子任务,每一个子任务会包装一个 Java 函数式接口实例,表示该子任务所要执行的动作。

image.png

CompletionStage接口

顾名思义,Stage 是阶段的意思。CompletionStage 它代表了某个同步或者异步计算的一个阶 段,或者是一系列异步任务中的一个子任务(或者阶段性任务)。
每个 CompletionStage 子任务所包装的可以是一个 Function、Consumer 或者 Runnable 函数式 接口实例。这三个常用的函数式接口的特点为:
(1)Function
Function 接口的唯一方法点是:有输入、有输出。包装了 Funtion 实例的 CompletionStage 子任务需要一个输入参数,并会产生一个输出结果到下一步。
(2)Runnable
Runnable 接口的唯一方法点是:无输入、无输出。包装了 Runnable 实例的 CompletionStage 子任务既不需要任何输入参数,也不会产生任何输出。
(3)Consumer
Consumer 接口的唯一方法点是:有输入、无输出。包装了 Consumer 实例的 CompletionStage子任务需要一个输入参数,但不会产生任何输出。
多个 CompletionStage 构成了一条任务流水线,一个环节执行完成了将结果可以移交给下一个环节(子任务)。多CompletionStage 子任务之间可以使用链式调用,下面是一个简单的例子:

oneStage.thenApply(x -> square(x)) 
 .thenAccept(y -> System.out.println(y)) 
 .thenRun(() -> System.out.println()) 

对以上例子中的 CompletionStage 子任务说明如下:
(1)oneStage 是一个 CompletionStage 子任务,这是一个前提。
(2)“x -> square(x)” 是一个 Function 类型的 Lamda 表达式,被 thenApply 方法包装成了一 个 CompletionStage 子任务,该子任务需要接收一个参数 x,然后会输出一个结果——x 的平方值。
(3)“y -> System.out.println(y)”是一个 Comsumer 类型的 Lamda 表达式,被 thenAccept 方法包装成了一个 CompletionStage 子任务,该子任务需要消耗上一个 Stage(子任务)的输出值, 但是此 Stage 并没有输出。
(4)“() -> System.out.println() ”是一个 Runnable 类型的 Lamda 表达式,被 thenRun 方法包装成了一个 CompletionStage 子任务,既不消耗上一个 Stage 的输出,也不产生结果。
CompletionStage 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另一个阶段。虽然一个 Stage 可以触发其他 Stage,但是并不能保证后续 Stage 的执行顺序。

使用runAsync和supplyAsync创建子任务

CompletionStage 子任务的创建是通过 CompletableFuture 完成的。CompletableFuture 类提供 了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletionStage 的方法。
CompletableFuture 定义了一组方法用于创建 CompletionStage 子任务(或者阶段性任务),基础的方法如下:

   //子任务包装一个 Runnable 实例,并使用 ForkJoinPool.commonPool()线程池去执行
    public static CompletableFuture<Void> runAsync(Runnable runnable)
        
    //子任务包装一个 Runnable 实例,并使用指定的 executor 线程池去执行
    public static CompletableFuture<Void> runAsync(
            Runnable runnable, Executor executor)

    //子任务包装一个 Supplier 实例,并使用 ForkJoinPool.commonPool()线程池去执行
    public static <U> CompletableFuture<U> supplyAsync(
            Supplier<U> supplier)

    //子任务包装一个 Supplier 实例,并使用指定的 executor 线程池去执行
    public static <U> CompletableFuture<U> supplyAsync(
            Supplier<U> supplier, Executor executor)

在 CompletableFuture 创建 CompletionStage 子任务时,如果没有指定 Executor 线程池,默认情况下 CompletionStage 会使用公共的 ForkJoinPool 线程池。
下面是两个简单的创建 CompletionStage 子任务的演示用例:

 //无返回值异步调用
    @Test
    public void runAsyncDemo() throws Exception
    {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
        {
            sleepSeconds(1);//模拟执行1秒
            Print.tco("run end ...");
        });

        //等待异步任务执行完成,现时等待2秒
        future.get(2, TimeUnit.SECONDS);
    }

    //有返回值异步调用
    @Test
    public void supplyAsyncDemo() throws Exception
    {
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() ->
        {
            long start = System.currentTimeMillis();
            sleepSeconds(1);//模拟执行1秒
            Print.tco("run end ...");
            return System.currentTimeMillis() - start;
        });

        //等待异步任务执行完成,现时等待2秒
        long time = future.get(2, TimeUnit.SECONDS);
        Print.tco("异步执行耗时(秒) = " + time / 1000);
    }
设置子任务回调钩子

可以为 CompletionStage 子任务设置特定的回调钩子,当的计算结果完成,或者抛出异常的 时候,可以执行这些特定的回调钩子。
设置的子任务回调钩子的函数,主要是下面的方法:

    //设置的子任务完成时的回调钩子
    public CompletableFuture<T> whenComplete(
            BiConsumer<? super T,? super Throwable> action)
    //设置的子任务完成时的回调钩子,可能不在同一线程执行
    public CompletableFuture<T> whenCompleteAsync(
            BiConsumer<? super T,? super Throwable> action)
    //设置的子任务完成时的回调钩子,提交给线程池 executor 执行
    public CompletableFuture<T> whenCompleteAsync(
            BiConsumer<? super T,? super Throwable> action,
            Executor executor)
    //设置的异常处理的回调钩子
    public CompletableFuture<T> exceptionally(
            Function<Throwable,? extends T> fn)

下面是一个简单的为 CompletionStage 子任务设置完成钩子和异常钩子的演示用例:

@Test
    public void whenCompleteDemo() throws Exception
    {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
        {
            sleepSeconds(1);//模拟执行1秒
            Print.tco("抛出异常!");
            throw new RuntimeException("发生异常");
            //Print.tco("run end ...");
        });
        //设置执行完成后的回调钩子
        future.whenComplete(new BiConsumer<Void, Throwable>()
        {
            @Override
            public void accept(Void t, Throwable action)
            {
                Print.tco("执行完成!");
            }
        });
        //设置发生异常后的回调钩子
        future.exceptionally(new Function<Throwable, Void>()
        {
            @Override
            public Void apply(Throwable t)
            {
                Print.tco("执行失败!" + t.getMessage());
                return null;
            }
        });
        future.get();
    }

调用 cancel()方法取消 CompletableFuture 时,任务被视为异常完成,completeExceptionally() 方法所设置的异常回调钩子也会被执行到。
如果没有设置异常回调钩子,发生内部异常时,会有两种情况发生:
(1)在使用 get()和 get(long, TimeUnit)方法启动任务时,如果遇到内部异常,则 get 方法会 抛出 ExecutionException(执行异常)
(2)在使用 join()和 getNow(T)启动任务时(大多数情况下都是如此),如果遇到内部异常,join()和 getNow(T)方法会抛出 CompletionException。

handler统一处理异常和结果

除了通过 whenComplete、exceptionally 设置完成钩子、异常钩子之外,还可以使用 handle 方 法统一处理结果和异常。
handle 方法有三个重载版本,三个版本的声明如下:

    //在执行任务的同一个线程中处理异常和结果
    public <U> CompletionStage<U> handle(
            BiFunction<? super T, Throwable, ? extends U> fn);
    //可能不在执行任务的同一个线程中处理异常和结果
    public <U> CompletionStage<U> handleAsync(
            BiFunction<? super T, Throwable, ? extends U> fn);
    //在指定线程池 executor 中处理异常和结果
    public <U> CompletionStage<U> handleAsync(
            BiFunction<? super T, Throwable, ? extends U> fn,
            Executor executor);

handle 方法的示例代码,具体如下:

@Test
    public void handleDemo() throws Exception
    {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
        {
            sleepSeconds(1);//模拟执行1秒
            Print.tco("抛出异常!");
            throw new RuntimeException("发生异常");
            //Print.tco("run end ...");
        });
        //设置执行完成后的回调钩子
        future.handle(new BiFunction<Void, Throwable, Void>()
        {

            @Override
            public Void apply(Void input, Throwable throwable)
            {
                if (throwable == null)
                {
                    Print.tcfo("没有发生异常!");

                } else
                {
                    Print.tcfo("sorry,发生了异常!");

                }
                return null;
            }
        });

        future.get();
    }
线程池的使用

默认情况下通过静态方法 runAsync 、supplyAsync 创建的 CompletableFuture 任务会使用公共的 ForkJoinPool 线程池,其默认的线程数是 CPU 的核数。当然,其线程数可以通过以下 JVM参数去设置:

option:-Djava.util.concurrent.ForkJoinPool.common.parallelism

问题是:如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 IO操作,就会导致线程池中所有线程都阻塞在 IO 操作上,从而造成线程饥饿,进而影响整个系统 的性能。所以,强烈建议大家根据不同的业务类型创建不同的线程池,以避免互相干扰。
所以,建议大家在生产环境使用时,根据不同的业务类型创建不同的线程池,以避免互相影响。前面第一章为大家介绍了三种线程池:IO 密集型任务线程池、CPU 密集型任务线程池、混合 型任务线程池。大家可以根据不同的任务类型,确定线程池的类型和线程数。
作为演示,这里使用“混合型任务线程池”执行 CompletableFuture 任务,具体的代码如下:

//有返回值异步调用
    @Test
    public void threadPoolDemo() throws Exception
    {
        //业务线程池
        ThreadPoolExecutor pool= ThreadUtil.getMixedTargetThreadPool();
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() ->
        {
            Print.tco("run begin ...");
            long start = System.currentTimeMillis();
            sleepSeconds(1);//模拟执行1秒
            Print.tco("run end ...");
            return System.currentTimeMillis() - start;
        },pool);

        //等待异步任务执行完成,现时等待2秒
        long time = future.get(2, TimeUnit.SECONDS);
        Print.tco("异步执行耗时(秒) = " + time / 1000);
    }

异步任务的串行执行

如果两个异步任务需要串行(当一个任务依赖另一个任务)执行,可以通过 CompletionStage 接口的 thenApply、thenAccept、thenRun 和 thenCompose 四个方法实现。

thenApply方法

thenApply 方法有三个重载版本,三个版本的声明如下:

    //后一个任务与前一个任务在同一个线程中执行
    public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
    //后一个任务与前一个任务可以不在同一个线程中执行
    public <U> CompletableFuture<U> thenApplyAsync(  Function<? super T,? extends U> fn)
    //后一个任务在指定的 executor 线程池中执行
    public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

thenApply 三个重载版本有一个共同的参数 fn,该参数表示待串行执行的第二个异步任务, 其类型为 Function。fn 的类型声明涉及到两个范型参数,具体如下:

  • 范型参数 T:上一个任务所返回结果的类型。
  • 范型参数 U:当前任务的返回值类型。

作为示例,使用 thenApply 分两步计算(10*10)*10,代码如下:

 @Test
    public void test6() throws ExecutionException, InterruptedException {
        CompletableFuture<Long> longCompletableFuture = CompletableFuture.supplyAsync(new Supplier<Long>() {
            @Override
            public Long get() {
                return 10l * 10l;
            }
        }).thenApply(new Function<Long, Long>() {
            @Override
            public Long apply(Long aLong) {
                return aLong * 10;
            }
        });
        Long aLong = longCompletableFuture.get();
        System.out.println("result:"+aLong);
    }

image.png

thenRun方法

thenRun 与 thenApply 方法不一样的是,不关心任务的处理结果。只要前一个任务执行完成, 就开始执行后一个串行任务。

thenApply 方法也有三个重载版本,三个版本的声明如下:

 //后一个任务与前一个任务在同一个线程中执行
    public CompletionStage<Void> thenRun(Runnable action);
    //后一个任务与前一个任务可以不在同一个线程中执行
    public CompletionStage<Void> thenRunAsync(Runnable action);
    //后一个任务在 executor 线程池中执行
    public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

从方法的声明可以看出,thenRun 方法同 thenApply 方法类似;不同的:前一个任务处理完成后,thenRun 并不会把计算的结果传给后一个任务,而且后一个任务也没有结果输出。
thenRun 系列方法里的 action 参数是 Runnable 类型,所以 thenRun 既不能接收参数也不支持返回值。

thenAccept方法

thenAccept 折衷了 thenRun、thenApply 的特点,使用此方法,后一个任务可以接收(或消费) 前一个任务的处理结果,但是后一个任务没有结果输出。
thenAccept 方法有三个重载版本,三个版本的声明如下:

   //后一个任务与前一个任务在同一个线程中执行
    public CompletionStage<Void> thenAccept(Consumer<? super T> action);
    //后一个任务与前一个任务可以不在同一个线程中执行
    public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
    //后一个任务在指定的 executor 线程池中执行
    public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

thenAccept 系列函数的回调参数为 action,其类型为 Consumer<? super T>接口,该接口的代 码如下:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer接口的 accept 可以接收一个参数,但是不支持返回值,所以 thenAccept 可以将 前一个任务的结果,并将该阶段性的结果,通过 void accept(T t)方法传递给到下一个任务。但是 Consumer接口的accept方法没有返回值,所以thenAccept也不能提供第二个任务的执行结果。

thenCompose方法

thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。
thenCompose 方法在功能上与 thenApply、thenAccept、thenRun 一样,可以对两个任务进行 串行的调度操作,第一个任务操作完成时,将其结果作为参数传递给第二个任务。
thenCompose 方法有三个重载版本,三个版本的声明如下:

 public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
 public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
 public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,Executor executor) ;

thenCompose 方法要求第二个任务的返回值是一个 CompletionStage 异步实例。因此,可以调 用 CompletableFuture.supplyAsync 方法,将第二个任务所要调用的普通异步方法,包装成一个 CompletionStage 异步实例。
作为演示,使用 thenCompose 分两步计算(10+10)*2,代码如下:

 @Test
    public void thenComposeDemo() throws Exception
    {
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>()
        {
            @Override
            public Long get()
            {
                long firstStep = 10L + 10L;
                Print.tco("firstStep outcome is " + firstStep);

                return firstStep;
            }
        }).thenCompose(new Function<Long, CompletionStage<Long>>()
        {
            @Override
            public CompletionStage<Long> apply(Long firstStepOutCome)
            {
                return CompletableFuture.supplyAsync(new Supplier<Long>()
                {
                    @Override
                    public Long get()
                    {
                        long secondStep = firstStepOutCome * 2;
                        Print.tco("secondStep outcome is " + secondStep);
                        return secondStep;
                    }
                });
            }

        });
        long result = future.get();
        Print.tco(" outcome is " + result);
    }

小总结

thenApply、thenRun、thenAccept这三类方法的不同之处,主要在于这三类方法的核心参数 fn、 action、consumer 的类型不同,分别为:Function<T, R>、Runnable、Consumer<? super T> 类型。
但是,thenCompose 方法与 thenApply 方法有本质的不同:
(1)thenCompose 的返回值是一个新的 CompletionStage 实例,可以持续用来进行下一轮 CompletionStage 任务的调度。
具体来说,thenCompose 返回的是包装了普通异步方法的 CompletionStage 任务实例,通过该 实例还可以进行下一轮CompletionStage 任务的调度和执行,比如可以持续进行 CompletionStage 链式(或者流式)调用。
(2)thenApply 的返回值则简单多了,直接就是第二个任务的普通异步方法的执行结果,其 返回类型与第二步执行的普通异步方法的返回类型相同;通过 thenApply 所返回的值,不能进行下一轮 CompletionStage 链式(或者流式)调用。

异步任务的合并执行

如果某个任务同时依赖另外两异步任务的执行结果,则需要对另外两异步任务进行合并。

thenCombine 方法

thenCombine 会把两个 CompletionStage 任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

    //合并第二步任务的 CompletionStage 实例,返回第三步任务的 CompletionStage
    public <U,V> CompletionStage<V> thenCombine(
            CompletionStage<? extends U> other, //待合并 CompletionStage 实例
            BiFunction<? super T,? super U,? extends V> fn); //第三步的逻辑
    //不一定在同一个线程中执行第三步任务的 CompletionStage 实例
    public <U,V> CompletionStage<V> thenCombineAsync(
            CompletionStage<? extends U> other,
            BiFunction<? super T,? super U,? extends V> fn);
    //第三步任务的 CompletionStage 实例在指定的 executor 线程池中执行
    public <U,V> CompletionStage<V> thenCombineAsync(
            CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,
            Executor executor);

thenCombine( )方法的调用者为第一步的 CompletionStage 实例;该方法的第一个参数为第二 步的 CompletionStage 实例;该方法的返回值为第三步的 CompletionStage 实例。逻辑上,thenCombine( )方法的功能是将第一步、第二步的结果,合并到第三步上。
thenCombine 系列方法有两个核心参数:

  • other 参数:表示待合并的第二步任务的 CompletionStage 实例。
  • fn 参数:表示第一个任务和第二个任务执行完成后,第三步的需要执行的逻辑。

fn 参数的类型为 BiFunction<? super T,? super U,? extends V>,该类型的声明涉及到三个范型 参数,具体如下:

  • 范型参数 T:表示第一个任务所返回结果的类型。
  • 范型参数 U:表示第二个任务所返回结果的类型。
  • 范型参数 V:表示第三个任务所返回结果的类型。

BiFunction<? super T,? super U,? extends V>的源码,具体如下:

@FunctionalInterface 
public interface BiFunction<T, U, R> { 
   R apply(T t, U u); 
} 

通过 BiFunction 的 apply 方法源码可以看出,BiFunction 的前两个范型参数 T、U 是输入参 数类型,BiFunction 的后一个范型参数 V 是输出参数的类型。
作为示例,接下来使用 thenCombine 分三步计算(10+10)*(10+10),代码如下:

 @Test
    public void thenCombineDemo() throws Exception
    {
        CompletableFuture<Integer> future1 =
                CompletableFuture.supplyAsync(new Supplier<Integer>()
                {
                    @Override
                    public Integer get()
                    {
                        Integer firstStep = 10 + 10;
                        Print.tco("firstStep outcome is " + firstStep);
                        return firstStep;
                    }
                });
        CompletableFuture<Integer> future2 =
                CompletableFuture.supplyAsync(new Supplier<Integer>()
                {
                    @Override
                    public Integer get()
                    {
                        Integer secondStep = 10 + 10;
                        Print.tco("secondStep outcome is " + secondStep);
                        return secondStep;
                    }
                });
        CompletableFuture<Integer> future3 = future1.thenCombine(future2,
                new BiFunction<Integer, Integer, Integer>()
                {
                    @Override
                    public Integer apply(Integer step1OutCome, Integer step2OutCome)
                    {
                        return step1OutCome * step2OutCome;
                    }
                });
        Integer result = future3.get();
        Print.tco(" outcome is " + result);
    }
thenAfterBoth方法

thenAcceptBoth 折衷了 runAfterBoth 和 thenCombine 方法的特点,使用该方法,第三个任务 可以接收其合并过来的第一个任务、第二个任务的处理结果,但是第三个任务(合并任务)却不能返回结果。
thenAcceptBoth 方法有三个重载版本,三个版本的声明如下:

    //合并第二步任务的 CompletionStage 实例,返回第三步任务的 CompletionStage
    public <U> CompletionStage<Void> thenAcceptBoth(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action);
    //功能与上一个函数相同,不一定在同一个线程执行第三步任务
    public <U> CompletionStage<Void> thenAcceptBothAsync(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action);
    //功能与上一个函数相同,在指定的 executor 线程池中执行第三步任务
    public <U> CompletionStage<Void> thenAcceptBothAsync(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action,
            Executor executor);

thenAcceptBoth 系列函数的第二参数为需要合并的第二步任务的 CompletionStage 实例。第三 参数为第三个任务的回调函数,该参数名称为 action,其类型为 BiConsumer<? super T, ? super U>接口,该接口的代码如下:

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

BiConsumer<? super T, ? super U> 接口的 accept 方法可以接收两个参数,但是不支持返回值。 所以 thenAcceptBoth 可以将前面的第一个任务、第二个任务的结果,作为阶段性的结果进行合并。
但是 BiConsumer<T, U>的 accept 方法没有返回值,所以 thenAccept 也不能提供第三个任务的执 行结果。

thenAcceptBoth方法

thenAcceptBoth 折衷了 runAfterBoth 和 thenCombine 方法的特点,使用该方法,第三个任务 可以接收其合并过来的第一个任务、第二个任务的处理结果,但是第三个任务(合并任务)却不 能返回结果。
thenAcceptBoth 方法有三个重载版本,三个版本的声明如下:

     //合并第二步任务的 CompletionStage 实例,返回第三步任务的 CompletionStage
    public <U> CompletionStage<Void> thenAcceptBoth(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action);
    //功能与上一个函数相同,不一定在同一个线程执行第三步任务
    public <U> CompletionStage<Void> thenAcceptBothAsync(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action);
    //功能与上一个函数相同,在指定的 executor 线程池中执行第三步任务
    public <U> CompletionStage<Void> thenAcceptBothAsync(
            CompletionStage<? extends U> other,
            BiConsumer<? super T, ? super U> action,
            Executor executor);

thenAcceptBoth 系列函数的第二参数为需要合并的第二步任务的 CompletionStage 实例。第三参数为第三个任务的回调函数,该参数名称为 action,其类型为 BiConsumer<? super T, ? super U> 接口,该接口的代码如下:

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

BiConsumer<? super T, ? super U> 接口的 accept 方法可以接收两个参数,但是不支持返回值。 所以 thenAcceptBoth 可以将前面的第一个任务、第二个任务的结果,作为阶段性的结果进行合并。
但是 BiConsumer<T, U>的 accept 方法没有返回值,所以 thenAccept 也不能提供第三个任务的执 行结果。

allOf等待所有的任务结束

CompletionStage 接口的 allOf 会等待所有的任务结束,用于合并所有的任务。thenCombine 只 能合并两个任务,如果需要合并多个异步任务,可以使用 allOf。一个简单的实例如下:

@Test
    public void allOfDemo() throws Exception
    {
        CompletableFuture<Void> future1 =
                CompletableFuture.runAsync(() -> Print.tco("模拟异步任务1"));

        CompletableFuture<Void> future2 =
                CompletableFuture.runAsync(() -> Print.tco("模拟异步任务2"));
        CompletableFuture<Void> future3 =
                CompletableFuture.runAsync(() -> Print.tco("模拟异步任务3"));
        CompletableFuture<Void> future4 =
                CompletableFuture.runAsync(() -> Print.tco("模拟异步任务4"));

        CompletableFuture<Void> all =
                CompletableFuture.allOf(future1, future2, future3, future4);
        all.join();
    }

异步任务的选择执行

CompletableFuture 对异步任务的选择执行,不是按照某种条件进行选择,而是按照执行速度进行选择:前面两并行任务,谁的结果返回速度快,其结果将作为第三步任务的输入。
对两个异步任务的选择,可以通过 CompletionStage 接口的 applyToEither、runAfterEither 和 acceptEither 三类方法实现。这三类方法的不同之处,还是在于这三类方法的核心参数 fn、action、 consumer 的类型不同,分别为:Function<T, R>、Runnable、Consumer<? super T> 类型。

applyToEither 方法

两个 CompletionStage 谁返回结果的速度快,applyToEither 就用这个最快的 CompletionStage 的结果进行下一步(第三步)的回调操作。
applyToEither 方法有三个重载版本,三个版本的声明如下:

    //和 other 任务进行速度 PK,最快返回的结果用于执行 fn 回调函数
    public <U> CompletionStage<U> applyToEither(
            CompletionStage<? extends T> other,Function<? super T, U> fn);
    //功能与上一个函数相同,不一定在同一个线程中执行 fn 回调函数
    public <U> CompletionStage<U> applyToEitherAsync(
            CompletionStage<? extends T> other,Function<? super T, U> fn);
    //功能与上一个函数相同,在指定线程执行 fn 回调函数
    public <U> CompletionStage<U> applyToEitherAsync(
            CompletionStage<? extends T> other,
            Function<? super T, U> fn,Executor executor);

applyToEither 系列函数的回调参数为 fn,其类型为接口 Function<T, R>,该接口的代码如 下:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function<T, R>接口既能接收输入参数也支持返回值。在 applyToEither 方法中,Function 的 输入参数为前两个 CompletionStage 中返回快的那个结果,Function 的输出值为最终的执行结果。
作为示例,接下来使用 applyToEither 随机选择(10+10)和(100+100)的结果,代码如下:

@Test
    public void applyToEitherDemo() throws Exception
    {
        CompletableFuture<Integer> future1 =
                CompletableFuture.supplyAsync(new Supplier<Integer>()
                {
                    @Override
                    public Integer get()
                    {
                        Integer firstStep = 10 + 10;
                        Print.tco("firstStep outcome is " + firstStep);
                        return firstStep;
                    }
                });
        CompletableFuture<Integer> future2 =
                CompletableFuture.supplyAsync(new Supplier<Integer>()
                {
                    @Override
                    public Integer get()
                    {
                        Integer secondStep = 100 + 100;
                        Print.tco("secondStep outcome is " + secondStep);
                        return secondStep;
                    }
                });
        CompletableFuture<Integer> future3 = future1.applyToEither(future2,
                new Function<Integer, Integer>()
                {
                    @Override
                    public Integer apply(Integer eitherOutCome)
                    {
                        return eitherOutCome;
                    }
                });
        Integer result = future3.get();
        Print.tco(" outcome is " + result);
    }

runAfterEither方法

runAfterEither 方法功能为:前面两个 CompletionStage 实例,任何一个完成了都会执行第三 步回调操作。三个任务的被调函数,都是 Runnable 类型。
runAfterEither 方法有三个重载版本,三个版本的声明如下:

//和 other 任务进行速度 PK,只要一个执行完成,就开始执行 fn 回调函数
    public CompletionStage<Void> runAfterEither(
            CompletionStage<?> other,Runnable action);
    //功能与上一个函数相同,不一定在同一个线程中执行 fn 回调函数
    public CompletionStage<Void> runAfterEitherAsync(
            CompletionStage<?> other,Runnable action);
    //功能与上一个函数相同,在指定线程执行 fn 回调函数
    public CompletionStage<Void> runAfterEitherAsync(
            CompletionStage<?> other,Runnable action,
            Executor executor);

runAfterEither ( )方法的调用者为第一步任务的 CompletionStage 实例;runAfterEither ( )方法 的第一个参数为第二步任务的 CompletionStage 实例;runAfterEither ( )方法的返回值为第三步任 务的 CompletionStage 实例。
使用 runAfterEither 方法,只要前面两个 CompletionStage 实例,其中任何一个执行完成,就 开始执行第三步的 CompletionStage 实例。

acceptEither方法

acceptEither 折中了 applyToEither 和 runAfterEither 方法的特点:两个 CompletionStage 谁返 回结果的速度快,acceptEither 就用这个最快的 CompletionStage 的结果作为下一步(第三步)的输入,但是,第三步没有输出。
acceptEither 方法有三个重载版本,三个版本的声明如下:

    //和 other 任务进行速度 PK,最快返回的结果用于执行 fn 回调函数
    public CompletionStage<Void> acceptEither(
            CompletionStage<? extends T> other,
            Consumer<? super T> action);
    //功能与上一个函数相同,不一定在同一个线程中执行 fn 回调函数
    public CompletionStage<Void> acceptEitherAsync(
            CompletionStage<? extends T> other,Consumer<? super T> action);
    //功能与上一个函数相同,在指定的 executor 线程池中执行第三步任务
    public CompletionStage<Void> acceptEitherAsync(
            CompletionStage<? extends T> other,
            Consumer<? super T> action,Executor executor);

acceptEither 系列函数的第二参数 other 为待进行速度比较的第二步任务的 CompletionStage 实例。第三参数为第三个任务的回调函数,该参数名称为 action,其类型为 Consumer<? super T>接口,该接口的代码如下:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer接口的 accept 可以接收一个参数,但是不支持返回值,所以 acceptEither 可以 将前面最快返回的阶段性结果,通过 void accept(T t)方法传递给到第三个任务。但是 Consumer接口的 accept 方法没有返回值,所以 acceptEither 也不能提供第三个任务的执行结果。

CompleteableFuture综合案例

使用CompletableFuture进行多个RPC调用
public class IntegrityDemo
{
    /**
     * 模拟模拟RPC调用1
     */
    public String rpc1()
    {
        //睡眠400ms,模拟执行耗时
        sleepMilliSeconds(600);
        Print.tcfo("模拟RPC调用:服务器 server 1");
        return "sth. from server 1";
    }

    /**
     * 模拟模拟RPC调用2
     */
    public String rpc2()
    {
        //睡眠400ms,模拟执行耗时
        sleepMilliSeconds(600);
        Print.tcfo("模拟RPC调用:服务器 server 2");
        return "sth. from server 2";
    }

    @Test
    public void rpcDemo() throws Exception
    {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->
        {
            return rpc1();
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> rpc2());
        CompletableFuture<String> future3 = future1.thenCombine(future2,
                (out1, out2) ->
                {
                    return out1 + " & " + out2;
                });
        String result = future3.get();
        Print.tco("客户端合并最终的结果:" + result);
    }
}
使用RxJava模拟RPC异步回调
@Test
    public void rxJavaDemo() throws Exception
    {
        Observable<String> observable1 = Observable.fromCallable(() ->
        {
            return rpc1();
        }).subscribeOn(Schedulers.newThread());
        Observable<String> observable2 = Observable
                .fromCallable(() -> rpc2()).subscribeOn(Schedulers.newThread());

        Observable.merge(observable1, observable2)
                .observeOn(Schedulers.newThread())
                .toList()
                .subscribe((result) -> Print.tco("客户端合并最终的结果:" + result));

        sleepSeconds(Integer.MAX_VALUE);
    }

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
【资源说明】 基于C++实现的HTTP服务器改进版源码+项目使用说明+详细注释.zip 1、技术架构 **本项目实现了基于Epoll管理连接、基于定时器处理非活动连接、基于线程池实现Reactor模式、基于cgi脚本处理http请求结果的HTTP服务器。主要框架如下:**\ ![](./image/newhttpd.jpg) 2、模块介绍 **1)主线程实现eventLoop**:主线程基于Reactor并通过Epoll管理,采用ET工作模式进行事件触发,事件注册包括监听、管道监控、读信息监控;\ **2)定时器处理非活动连接**:\ **①基于升序链表的定时器**:将每个需要监控的连接注册为一个时间结点,每个结点包括双向指针以及期待的时间和回调函数指针;包含添加、删除以及调整结点;回调函数主要实现对当前连接的close;\ **②基于信号和管道的定时事件处理**:建立监听数据集(新连接会加入一个数据集和时间结点,新信息读入会读取数据集并修改时间结点),基于sigaction形式实现对信号和信号处理函数的绑定,信号处理函数向管道发送信号消息,主线程监听到管道消息读入后判断信号类别,并进行关闭连接操作。\ **3)Http响应处理**:基于tinyhttpd进行修改,捕获GET、POST方法,基于cgi脚本(python撰写)实现post请求响应,基于多进程机制并通过双通道实现进程间通信,并用waitpid进行子进程管控。具体结构如下图所示:\ ![](./image/httpd.jpg) \ **4)线程池**:基于C++的生产者消费者模式的并发开发,具体技术运用如下:\ **①线程池底层结构**:线程池创建相当于消费者,队列添加相当于生产者,通过vector维护线程池,通过queue<function<>>维护任务队列;构造函数实现线程池创建并开始运行,enqueue函数实现消息队列,通过future实现异步工作的lambda函数的传递;\ **②同步机制实现**:基于unique_lock以及condition_variable实现同步和互斥,符合RAII原则;\ **5)简单客户端**:(可以通过浏览器进行服务端访问,也可以通过该客户端实现交互以及非活动连接处理的测试)\ **①基于POLL的IO复用**:对管道和连接进行事件监听和处理;\ **②基于双管道的简易CGI实现**:修改stdin的定向为管道写端,实现终端对客户端的直接输入和对服务端的发送;\ **6)改进方向**:待进行压力测试并提高抗压性能、可处理的HTTP请求较为简单(数据体的处理还待增加以及CGI功能的完善)、内存池。 3、编译使用 **服务端**:进入linux系统后,进入当前文件夹,首先修改可执行权限,然后通过CMake编译执行即可: ~~~c cd minghttp chmod 600 test.html chmod 600 post.html chmod +X post.cgi cd .. cmake . make ./httpserver ~~~ **客户端**:一方面可以通过浏览器直接进行服务器访问,一方面可以使用自己创建的客户端进行连接和消息互传(使用方案如下): ~~~c g++ simclient.cpp ./a.out ip port ~~~ ![](./image/out.jpg) 4、呈现效果 上一部分的图片已经展现定时器处理非活动连接的效果;\ 1)项目默认端口号为8000,ip地址需要通过ifconfig进行查看;\ 2)将ip和端口号进行替换输入,如下输入后可以得到如下界面:\ ![](./image/test.jpg)\ 3)POST的界面信息:\ ![](./image/jie.jpg)\ 4)POST的CGI脚本回显,基于python进行撰写,内容传输为html语言:\ ![](./image/cgi.jpg)\ 5)定时器的相关讯息也可以得到:可以看到5秒信号的定时器信息输出:\ ![](./image/jie1.jpg) 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
精通并发与 netty 视频教程(2018)视频教程。 精通并发与netty视频教程(2018)视频教程 netty视频教程 Java视频教程目录: 1_学习的要义 2_Netty宏观理解 3_Netty课程大纲深度解读 4_项目环境搭建与Gradle配置 5_Netty执行流程分析与重要组件介绍 6_Netty回调与Channel执行流程分析 7_Netty的Socket编程详解 8_Netty多客户端连接与通信 9_Netty读写检测机制与长连接要素 10_Netty对WebSocket的支援 11_Netty实现服务器端与客户端的长连接通信 12_Google Protobuf详解 13_定义Protobuf文件及消息详解 14_Protobuf完整实例详解 15_Protobuf集成Netty与多协议消息传 递 16_Protobuf多协议消息支援与工程最佳实践 17_Protobuf使用最佳实践与Apache Thrift介绍 18_Apache Thrift应用详解与实例剖析 19_Apache Thrift原理与架构解析 20_通过Apache Thrift实现Java与Python的RPC调用 21_gRPC深入详解 22_gRPC实践 23_Gradle Wrapper在Gradle项目构建中的最佳实践 24_gRPC整合Gradle与代码生成 25_gRPC通信示例与JVM回调钩子 26_gRPC服务器流式调用实现 27_gRPC双向流式数据通信详解 28_gRPC与Gradle流畅整合及问题解决的完整过程与思考 29_Gradle插件问题解决方案与Nodejs环境搭建 30_通过gRPC实现Java与Nodejs异构平台的RPC调用 31_gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC通信 32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_Reactor模式5大角色彻底分析 62_Reactor模式组件调用关系全景分析 63_Reactor模式与Netty组件对比及Acceptor组件的作用分析 64_Channel与ChannelPipeline关联关系及模式运用 65_ChannelPipeline创建时机与高级拦截过滤器模式的运用 66_Netty常量池实现及ChannelOption与Attribute作用分析 67_Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 68_Netty核心四大组件关系与构建方式深度解读 69_Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 70_Channel注册流程深度解读 71_Channel选择器工厂与轮询算法及注册底层实现 72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程中常见且重要事项分析 77_Java NIO Buffer总结回顾与难点拓展 78_Netty数
第1讲:学习的要义 第2讲:Netty宏观理解 第3讲:Netty课程大纲深度解读 第4讲:项目环境搭建与Gradle配置 第5讲:Netty执行流程分析与重要组件介绍 第6讲:Netty回调与Channel执行流程分析 第7讲:Netty的Socket编程详解 第8讲:Netty多客户端连接与通信 第9讲:Netty读写检测机制与长连接要素 第10讲:Netty对WebSocket的支援 第11讲:Netty实现服务器端与客户端的长连接通信 第12讲:Google Protobuf详解 第13讲:定义Protobuf文件及消息详解 第14讲:Protobuf完整实例详解 第15讲:Protobuf集成Netty与多协议消息传递 第16讲:Protobuf多协议消息支援与工程最佳实践 第17讲:Protobuf使用最佳实践与Apache Thrift介绍 第18讲:Apache Thrift应用详解与实例剖析 第19讲:Apache Thrift原理与架构解析 第20讲:通过Apache Thrift实现Java与Python的RPC调用 第21讲:gRPC深入详解 第22讲:gRPC实践 第23讲:Gradle Wrapper在Gradle项目构建中的最佳实践 第24讲:gRPC整合Gradle与代码生成 第25讲:gRPC通信示例与JVM回调钩子 第26讲:gRPC服务器流式调用实现 第27讲:gRPC双向流式数据通信详解 第28讲:gRPC与Gradle流畅整合及问题解决的完整过程与思考 第29讲:Gradle插件问题解决方案与Nodejs环境搭建 第30讲:通过gRPC实现Java与Nodejs异构平台的RPC调用 第31讲:gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC调用 第32讲:IO体系架构系统回顾与装饰模式的具体应用 第33讲:Java NIO深入详解与体系分析 第34讲:Buffer中各重要状态属性的含义与关系图解 第35讲:Java NIO核心类源码解读与分析 第36讲:文件通道用法详解 第37讲:Buffer深入详解 第38讲:NIO堆外内存与零拷贝深入讲解 第39讲:NIO中Scattering与Gathering深度解析 第40讲:Selector源码深入分析 第41讲:NIO网络访问模式分析 第42讲:NIO网络编程实例剖析 第43讲:NIO网络编程深度解析 第44讲:NIO网络客户端编写详解 第45讲:深入探索Java字符集编解码 第46讲:字符集编解码全方位解析 第47讲:Netty服务器与客户端编码模式回顾及源码分析准备 第48讲:Netty与NIO系统总结及NIO与Netty之间的关联关系分析 第49讲:零拷贝深入剖析及用户空间与内核空间切换方式 第50讲:零拷贝实例深度剖析 第51讲:NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 第52讲:NioEventLoopGroup源码分析与线程数设定 第53讲:Netty对Executor的实现机制源码分析 第54讲:Netty服务端初始化过程与反射在其中的应用分析 第55讲:Netty提供的Future与ChannelFuture优势分析与源码讲解 第56讲:Netty服务器地址绑定底层源码分析 第57讲:Reactor模式透彻理解及其在Netty中的应用 第58讲:Reactor模式与Netty之间的关系详解 第59讲:Acceptor与Dispatcher角色分析 第60讲:Netty的自适应缓冲区分配策略与堆外内存创建方式 第61讲:Reactor模式5大角色彻底分析 第62讲:Reactor模式组件调用关系全景分析 第63讲:Reactor模式与Netty组件对比及Acceptor组件的作用分析 第64讲:Channel与ChannelPipeline关联关系及模式运用 第65讲:ChannelPipeline创建时机与高级拦截过滤器模式的运用 第66讲:Netty常量池实现及ChannelOption与Attribute作用分析 第67讲:Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 第68讲:Netty核心四大组件关系与构建方式深度解读 第69讲:Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 第70讲:Channel注册流程深度解读 第71讲:Channel选择器工厂与轮询算法及注册底层实现 第72讲:Netty线程模型深度解读与架构设计原则 第73讲:Netty底层架构系统总结与应用实践 第74讲:Netty对于异步读写操作的架构思想与观察者模式的重要应用 第75讲:适配器模式与模板方法模式在入站处理器中的应用 第76讲:Netty项目开发过程中常见且重要事项分析 第77讲:Java NIO Buffer总结回顾与难点拓展 第78讲:Netty数据容器ByteBuf底层数据结构深度剖析 第79讲:Netty的ByteBuf底层实现大揭秘 第80讲:Netty复合缓冲区详解与3种缓冲区适用场景分析 第81讲:Netty引用计数的实现机制与自旋锁的使用技巧 第82讲:Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析 第83讲:AtomicIntegerFieldUpdater实例演练与volatile关键字分析 第84讲:Netty引用计数注意事项与内存泄露检测方式 第85讲:Netty编解码器剖析与入站出站处理器详解 第86讲:Netty自定义编解码器与TCP粘包拆包问题 第87讲:Netty编解码器执行流程深入分析 第88讲:ReplayingDecoder源码分析与特性解读 第89讲:Netty常见且重要编解码器详解 第90讲:TCP粘包与拆包实例演示及分析 第91讲:Netty自定义协议与TCP粘包拆包问题解决之道 第92讲:精通并发与Netty课程总结与展望
第1部分概述 1 1 交易型系统设计的一些原则 2 1.1 高并发原则 3 1.1.1 无状态 3 1.1.2 拆分 3 1.1.3 服务化 4 1.1.4 消息队列 4 1.1.5 数据异构 6 1.1.6 缓存银弹 7 1.1.7 并发化 9 1.2 高可用原则 10 1.2.1 降级 10 1.2.2 限流 11 1.2.3 切流量 12 1.2.4 可回滚 12 1.3 业务设计原则 12 1.3.1 防重设计 13 1.3.2 幂等设计 13 1.3.3 流程可定义 13 1.3.4 状态与状态机 13 1.3.5 后台系统操作可反馈 14 1.3.6 后台系统审批化 14 1.3.7 文档和注释 14 1.3.8 备份 14 1.4 总结 14 第2部分高可用 17 2 负载均衡与反向代理 18 2.1 upstream配置 20 2.2 负载均衡算法 21 2.3 失败重试 23 2.4 健康检查 24 2.4.1 TCP心跳检查 24 2.4.2 HTTP心跳检查 25 2.5 其他配置 25 2.5.1 域名上游服务器 25 2.5.2 备份上游服务器 26 2.5.3 不可用上游服务器 26 2.6 长连接 26 2.7 HTTP反向代理示例 29 2.8 HTTP动态负载均衡 30 2.8.1 Consul+Consul-template 31 2.8.2 Consul+OpenResty 35 2.9 Nginx四层负载均衡 39 2.9.1 静态负载均衡 39 2.9.2 动态负载均衡 41 参考资料 42 3 隔离术 43 3.1 线程隔离 43 3.2 进程隔离 45 3.3 集群隔离 45 3.4 机房隔离 46 3.5 读写隔离 47 3.6 动静隔离 48 3.7 爬虫隔离 49 3.8 热点隔离 50 3.9 资源隔离 50 3.10 使用Hystrix实现隔离 51 3.10.1 Hystrix简介 51 3.10.2 隔离示例 52 3.11 基于Servlet 3实现请求隔离 56 3.11.1 请求解析和业务处理线程池分离 57 3.11.2 业务线程池隔离 58 3.11.3 业务线程池监控/运维/降级 58 3.11.4 如何使用Servlet 3异步化 59 3.11.5 一些Servlet 3异步化压测数据 64 4 限流详解 66 4.1 限流算法 67 4.1.1 令牌桶算法 67 4.1.2 漏桶算法 68 4.2 应用级限流 69 4.2.1 限流总并发/连接/请求数 69 4.2.2 限流总资源数 70 4.2.3 限流某个接口的总并发/请求数 70 4.2.4 限流某个接口的时间窗请求数 70 4.2.5 平滑限流某个接口的请求数 71 4.3 分布式限流 75 4.3.1 Redis+Lua实现 76 4.3.2 Nginx+Lua实现 77 4.4 接入层限流 78 4.4.1 ngx_http_limit_conn_module 78 4.4.2 ngx_http_limit_req_module 80 4.4.3 lua-resty-limit-traffic 88 4.5 节流 90 4.5.1 throttleFirst/throttleLast 90 4.5.2 throttleWithTimeout 91 参考资料 92 5 降级特技 93 5.1 降级预案 93 5.2 自动开关降级 95 5.2.1 超时降级 95 5.2.2 统计失败次数降级 95 5.2.3 故障降级 95 5.2.4 限流降级 95 5.3 人工开关降级 96 5.4 读服务降级 96 5.5 写服务降级 97 5.6 多级降级 98 5.7 配置中心 100 5.7.1 应用层API封装 100 5.7.2 配置文件实现开关配置 101 5.7.3 配置中心实现开关配置 102 5.8 使用Hystrix实现降级 106 5.9 使用Hystrix实现熔断 108 5.9.1 熔断机制实现 108 5.9.2 配置示例 112 5.9.3 采样统计 113 6 超时与重试机制 117 6.1 简介 117 6.2 代理层超时与重试 119 6.2.1 Nginx 119 6.2.2 Twemproxy 126 6.3 Web容器超时 127 6.4 中间件客户端超时与重试 127 6.5 数据库客户端超时 131 6.6 NoSQL客户端超时 134 6.7 业务超时 135 6.8 前端Ajax超时 135 6.9 总结 136 6.10 参考资料 137 7 回滚机制 139 7.1 事务回滚 139 7.2 代码库回滚 140 7.3 部署版本回滚 141 7.4 数据版本回滚 142 7.5 静态资源版本回滚 143 8 压测与预案 145 8.1 系统压测 145 8.1.1 线下压测 146 8.1.2 线上压测 146 8.2 系统优化和容灾 147 8.3 应急预案 148 第3部分高并发 153 9 应用级缓存 154 9.1 缓存简介 154 9.2 缓存命中率 155 9.3 缓存回收策略 155 9.3.1 基于空间 155 9.3.2 基于容量 155 9.3.3 基于时间 155 9.3.4 基于Java对象引用 156 9.3.5 回收算法 156 9.4 Java缓存类型 156 9.4.1 堆缓存 158 9.4.2 堆外缓存 162 9.4.3 磁盘缓存 162 9.4.4 分布式缓存 164 9.4.5 多级缓存 166 9.5 应用级缓存示例 167 9.5.1 多级缓存API封装 167 9.5.2 NULL Cache 170 9.5.3 强制获取最新数据 170 9.5.4 失败统计 171 9.5.5 延迟报警 171 9.6 缓存使用模式实践 172 9.6.1 Cache-Aside 173 9.6.2 Cache-As-SoR 174 9.6.3 Read-Through 174 9.6.4 Write-Through 176 9.6.5 Write-Behind 177 9.6.6 Copy Pattern 181 9.7 性能测试 181 9.8 参考资料 182 10 HTTP缓存 183 10.1 简介 183 10.2 HTTP缓存 184 10.2.1 Last-Modified 184 10.2.2 ETag 190 10.2.3 总结 192 10.3 HttpClient客户端缓存 192 10.3.1 主流程 195 10.3.2 清除无效缓存 195 10.3.3 查找缓存 196 10.3.4 缓存未命中 198 10.3.5 缓存命中 198 10.3.6 缓存内容陈旧需重新验证 202 10.3.7 缓存内容无效需重新执行请求 205 10.3.8 缓存响应 206 10.3.9 缓存头总结 207 10.4 Nginx HTTP缓存设置 208 10.4.1 expires 208 10.4.2 if-modified-since 209 10.4.3 nginx proxy_pass 209 10.5 Nginx代理层缓存 212 10.5.1 Nginx代理层缓存配置 212 10.5.2 清理缓存 215 10.6 一些经验 216 参考资料 217 11 多级缓存 218 11.1 多级缓存介绍 218 11.2 如何缓存数据 220 11.2.1 过期与不过期 220 11.2.2 维度化缓存与增量缓存 221 11.2.3 大Value缓存 221 11.2.4 热点缓存 221 11.3 分布式缓存与应用负载均衡 222 11.3.1 缓存分布式 222 11.3.2 应用负载均衡 222 11.4 热点数据与更新缓存 223 11.4.1 单机全量缓存+主从 223 11.4.2 分布式缓存+应用本地热点 224 11.5 更新缓存与原子性 225 11.6 缓存崩溃与快速修复 226 11.6.1 取模 226 11.6.2 一致性哈希 226 11.6.3 快速恢复 226 12 连接池线程池详解 227 12.1 数据库连接池 227 12.1.1 DBCP连接池配置 228 12.1.2 DBCP配置建议 233 12.1.3 数据库驱动超时实现 234 12.1.4 连接池使用的一些建议 235 12.2 HttpClient连接池 236 12.2.1 HttpClient 4.5.2配置 236 12.2.2 HttpClient连接池源码分析 240 12.2.3 HttpClient 4.2.3配置 241 12.2.4 问题示例 243 12.3 线程池 244 12.3.1 Java线程池 245 12.3.2 Tomcat线程池配置 248 13 异步并发实战 250 13.1 同步阻塞调用 251 13.2 异步Future 252 13.3 异步Callback 253 13.4 异步编排CompletableFuture 254 13.5 异步Web服务实现 257 13.6 请求缓存 259 13.7 请求合并 261 14 如何扩容 266 14.1 单体应用垂直扩容 267 14.2 单体应用水平扩容 267 14.3 应用拆分 268 14.4 数据库拆分 271 14.5 数据库分库分表示例 275 14.5.1 应用层还是中间件层 275 14.5.2 分库分表策略 277 14.5.3 使用sharding-jdbc分库分表 279 14.5.4 sharding-jdbc分库分表配置 279 14.5.5 使用sharding-jdbc读写分离 283 14.6 数据异构 284 14.6.1 查询维度异构 284 14.6.2 聚合数据异构 285 14.7 任务系统扩容 285 14.7.1 简单任务 285 14.7.2 分布式任务 287 14.7.3 Elastic-Job简介 287 14.7.4 Elastic-Job-Lite功能与架构 287 14.7.5 Elastic-Job-Lite示例 288 15 队列术 295 15.1 应用场景 295 15.2 缓冲队列 296 15.3 任务队列 297 15.4 消息队列 297 15.5 请求队列 299 15.6 数据总线队列 300 15.7 混合队列 301 15.8 其他队列 302 15.9 Disruptor+Redis队列 303 15.9.1 简介 303 15.9.2 XML配置 304 15.9.3 EventWorker 305 15.9.4 EventPublishThread 307 15.9.5 EventHandler 308 15.9.6 EventQueue 308 15.10 下单系统水平可扩展架构 311 15.10.1 下单服务 313 15.10.2 同步Worker 313 15.11 基于Canal实现数据异构 314 15.11.1 Mysql主从复制 315 15.11.2 Canal简介 316 15.11.3 Canal示例 318 第4部分案例 323 16 构建需求响应式亿级商品详情页 324 16.1 商品详情页是什么 324 16.2 商品详情页前端结构 325 16.3 我们的性能数据 327 16.4 单品页流量特点 327 16.5 单品页技术架构发展 327 16.5.1 架构1.0 328 16.5.2 架构2.0 328 16.5.3 架构3.0 330 16.6 详情页架构设计原则 332 16.6.1 数据闭环 332 16.6.2 数据维度化 333 16.6.3 拆分系统 334 16.6.4 Worker无状态化+任务化 334 16.6.5 异步化+并发化 335 16.6.6 多级缓存化 335 16.6.7 动态化 336 16.6.8 弹性化 336 16.6.9 降级开关 336 16.6.10 多机房多活 337 16.6.11 多种压测方案 338 16.7 遇到的一些坑和问题 339 16.7.1 SSD性能差 339 16.7.2 键值存储选型压测 340 16.7.3 数据量大时JIMDB同步不动 342 16.7.4 切换主从 342 16.7.5 分片配置 342 16.7.6 模板元数据存储HTML 342 16.7.7 库存接口访问量600w/分钟 343 16.7.8 微信接口调用量暴增 344 16.7.9 开启Nginx Proxy Cache性能不升反降 344 16.7.10 配送至读服务因依赖太多,响应时间偏慢 344 16.7.11 网络抖动时,返回502错误 346 16.7.12 机器流量太大 346 16.8 其他 347 17 京东商品详情页服务闭环实践 348 17.1 为什么需要统一服务 348 17.2 整体架构 349 17.3 一些架构思路和总结 350 17.3.1 两种读服务架构模式 351 17.3.2 本地缓存 352 17.3.3 多级缓存 353 17.3.4 统一入口/服务闭环 354 17.4 引入Nginx接入层 354 17.4.1 数据校验/过滤逻辑前置 354 17.4.2 缓存前置 355 17.4.3 业务逻辑前置 355 17.4.4 降级开关前置 355 17.4.5 AB测试 356 17.4.6 灰度发布/流量切换 356 17.4.7 监控服务质量 356 17.4.8 限流 356 17.5 前端业务逻辑后置 356 17.6 前端接口服务端聚合 357 17.7 服务隔离 359 18 使用OpenResty开发高性能Web应用 360 18.1 OpenResty简介 361 18.1.1 Nginx优点 361 18.1.2 Lua的优点 361 18.1.3 什么是ngx_lua 361 18.1.4 开发环境 362 18.1.5 OpenResty生态 362 18.1.6 场景 362 18.2 基于OpenResty的常用架构模式 363 18.2.1 负载均衡 363 18.2.2 单机闭环 364 18.2.3 分布式闭环 367 18.2.4 接入网关 368 18.2.5 核心接入Nginx功能 369 18.2.6 业务Nginx功能 369 18.2.7 Web应用 370 18.3 如何使用OpenResty开发Web应用 371 18.3.1 项目搭建 371 18.3.2 启停脚本 372 18.3.3 配置文件 372 18.3.4 nginx.conf配置文件 373 18.3.5 Nginx项目配置文件 373 18.3.6 业务代码 374 18.3.7 模板 374 18.3.8 公共Lua库 375 18.3.9 功能开发 375 18.4 基于OpenResty的常用功能总结 375 18.5 一些问题 376 19 应用数据静态化架构高性能单页Web应用 377 19.1 整体架构 378 19.1.1 CMS系统 379 19.1.2 前端展示系统 380 19.1.3 控制系统 380 19.2 数据和模板动态化 381 19.3 多版本机制 381 19.4 异常问题 382 20 使用OpenResty开发Web服务 383 20.1 架构 383 20.2 单DB架构 384 20.2.1 DB+Cache/数据库读写分离架构 384 20.2.2 OpenResty+Local Redis+Mysql集群架构 385 20.2.3 OpenResty+Redis集群+Mysql集群架构 386 20.3 实现 387 20.3.1 后台逻辑 388 20.3.2 前台逻辑 388 20.3.3 项目搭建 389 20.3.4 Redis+Twemproxy配置 389 20.3.5 Mysql+Atlas配置 390 20.3.6 Java+Tomcat安装 394 20.3.7 Java+Tomcat逻辑开发 395 20.3.8 Nginx+Lua逻辑开发 401 21 使用OpenResty开发商品详情页 405 21.1 技术选型 407 21.2 核心流程 408 21.3 项目搭建 408 21.4 数据存储实现 410 21.4.1 商品基本信息SSDB集群配置 410 21.4.2 商品介绍SSDB集群配置 413 21.4.3 其他信息Redis配置 417 21.4.4 集群测试 418 21.4.5 Twemproxy配置 419 21.5 动态服务实现 422 21.5.1 项目搭建 422 21.5.2 项目依赖 422 21.5.3 核心代码 423 21.5.4 基本信息服务 424 21.5.5 商品介绍服务 426 21.5.6 其他信息服务 426 21.5.7 辅助工具 427 21.5.8 web.xml配置 428 21.5.9 打WAR包 428 21.5.10 配置Tomcat 428 21.5.11 测试 429 21.5.12 Nginx配置 429 21.5.13 绑定hosts测试 430 21.6 前端展示实现 430 21.6.1 基础组件 430 21.6.2 商品介绍 432 21.6.4 前端展示 434 21.6.5 测试 442
精通并发与netty视频教程(2018)视频教程 netty视频教程 Java视频教程目录: 1_学习的要义 2_Netty宏观理解 3_Netty课程大纲深度解读 4_项目环境搭建与Gradle配置 5_Netty执行流程分析与重要组件介绍 6_Netty回调与Channel执行流程分析 7_Netty的Socket编程详解 8_Netty多客户端连接与通信 9_Netty读写检测机制与长连接要素 10_Netty对WebSocket的支援 11_Netty实现服务器端与客户端的长连接通信 12_Google Protobuf详解 13_定义Protobuf文件及消息详解 14_Protobuf完整实例详解 15_Protobuf集成Netty与多协议消息传递 16_Protobuf多协议消息支援与工程最佳实践 17_Protobuf使用最佳实践与Apache Thrift介绍 18_Apache Thrift应用详解与实例剖析 19_Apache Thrift原理与架构解析 20_通过Apache Thrift实现Java与Python的RPC调用 21_gRPC深入详解 22_gRPC实践 23_Gradle Wrapper在Gradle项目构建中的最佳实践 24_gRPC整合Gradle与代码生成 25_gRPC通信示例与JVM回调钩子 26_gRPC服务器流式调用实现 27_gRPC双向流式数据通信详解 28_gRPC与Gradle流畅整合及问题解决的完整过程与思考 29_Gradle插件问题解决方案与Nodejs环境搭建 30_通过gRPC实现Java与Nodejs异构平台的RPC调用 31_gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC通信 32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_Reactor模式5大角色彻底分析 62_Reactor模式组件调用关系全景分析 63_Reactor模式与Netty组件对比及Acceptor组件的作用分析 64_Channel与ChannelPipeline关联关系及模式运用 65_ChannelPipeline创建时机与高级拦截过滤器模式的运用 66_Netty常量池实现及ChannelOption与Attribute作用分析 67_Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 68_Netty核心四大组件关系与构建方式深度解读 69_Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 70_Channel注册流程深度解读 71_Channel选择器工厂与轮询算法及注册底层实现 72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程中常见且重要事项分析 77_Java NIO Buffer总结回顾与难点拓展 78_Netty数据容器ByteBuf底层数据结构深度剖析 79_Netty
在不使用信号槽的情况下,我们可以使用线程和回调函数来实现观察者模式异步通信。 具体实现步骤如下: 1. 定义一个观察者类,其中包含一个回调函数指针,用于处理被观察者发出的事件。 2. 定义一个被观察者类,其中包含一个观察者的指针列表,以及一个通知观察者的函数。 3. 在被观察者类中,通知观察者时,使用线程来异步执行通知操作。 4. 在观察者类中,将自己的回调函数指针注册到被观察者的观察者指针列表中。 5. 当被观察者需要通知观察者时,遍历观察者指针列表,异步执行每个观察者的回调函数。 下面是一个简单的示例代码: ``` // 观察者类 class Observer { public: virtual void handleMessage(const QString& message) = 0; }; // 被观察者类 class Subject { public: void addObserver(Observer* observer) { m_observers.append(observer); } void removeObserver(Observer* observer) { m_observers.removeOne(observer); } void sendMessage(const QString& message) { // 使用线程异步执行通知操作 QFuture<void> future = QtConcurrent::run([=]() { for (Observer* observer : m_observers) { observer->handleMessage(message); } }); } private: QList<Observer*> m_observers; }; // 具体的观察者类 class ConcreteObserver : public Observer { public: void handleMessage(const QString& message) override { qDebug() << "Received message: " << message; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 创建观察者和被观察者对象 ConcreteObserver observer; Subject subject; // 注册观察者的回调函数到被观察者的观察者指针列表中 subject.addObserver(&observer); // 发送消息 subject.sendMessage("Hello World!"); return a.exec(); } ``` 在上面的示例代码中,当被观察者需要通知观察者时,使用线程异步执行通知操作,遍历观察者指针列表,异步执行每个观察者的回调函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值