异步代码走读CompletableFuture原理剖析

问一问ChatGpt

了解异步之前,先熟悉一下异步的概念,来自gpt:

异步和同步是两种不同的执行模式,用于描述任务的执行方式和调度方式。

  1. 同步执行(Synchronous Execution):在同步执行中,任务按照顺序依次执行,每个任务的执行必须等待前一个任务完成。在同步执行中,任务的执行是阻塞的,即一个任务的执行会阻塞当前线程,直到任务完成才能继续执行下一个任务。同步执行适用于任务之间有依赖关系,需要按照顺序执行的场景。

  2. 异步执行(Asynchronous Execution):在异步执行中,任务可以并发或并行地执行,不需要等待前一个任务完成。在异步执行中,任务的执行是非阻塞的,即一个任务的执行不会阻塞当前线程,可以立即返回并继续执行后续的任务。异步执行适用于任务之间相互独立,可以并发执行的场景。

区别:

  • 阻塞 vs 非阻塞:同步执行是阻塞的,一个任务的执行会阻塞当前线程;异步执行是非阻塞的,一个任务的执行不会阻塞当前线程。

  • 顺序 vs 并发/并行:同步执行按照顺序依次执行任务;异步执行可以并发或并行地执行任务。

  • 等待 vs 不等待:同步执行需要等待前一个任务完成才能执行下一个任务;异步执行不需要等待前一个任务完成,可以立即执行后续的任务。

  • 调度方式:同步执行由调用方主动触发任务的执行;异步执行可以通过回调、Future/Promise、事件驱动等方式来触发任务的执行。

在编程中,异步执行通常可以提高系统的并发性和响应性,避免阻塞和等待,提高任务的执行效率。然而,异步执行也需要考虑线程安全、并发控制和结果处理等问题。同步执行则更加直观和简单,适用于简单的顺序执行场景。选择使用异步还是同步执行取决于具体的需求和场景。

在读这篇文章原理之前,大家可以参考看一下 美团团队的图解分享,相信这样大家更容易读懂

图1 CompletableFuture

CompletableFuture主要实现了Future 和CompletionStage 接口,其中Future提供异步计算结果,CompletionStage则提供thenApply、thenApplyAsync、thenAccept、thenCombine、thenCompose、whenComplete等等编排回调的方法,内部基于回调触发各个依赖的future,可以把CompletionStage理解为异步中的一个stage。而诸如supplyAsync和runAsync等定义异步任务的方法都是在CompletableFuture内部实现,主要作用是定义异步任务,任务接口实现可以是Supplier 代表有返回,也可以是Runnable 无返回值,如supplyAsync和runAsync

CompletableFuture原理浅析

下面以下面代码示例,简单介绍一下CompletableFuture的执行原理,如有错误,请各位大佬指出

// 创建一个CompletableFuture对象,并使用指定的线程池执行异步任务
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        // 在后台线程中执行耗时操作
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 42;
});
// future2 依赖future1的结果,并且返回hello,thenApply的操作可以认为是往future1中注册回调,很多刚开始上手的同学,上来就接触lambda表达式,而忽略了带有accept和apply方法其实是
// 实现了Consumer 和 Function函数,仅仅是应用场景不同,内部逻辑原理相似,同理supplyAsync方法其实是传入Supplier接口,实现其get()方法
CompletableFuture<String> future2 = future1.thenApply(new Function<Integer, String>() {
        @Override
        public String apply(Integer integer) {
            return integer + "hello";
        }
});
// 同理,往future2中注册回调
future2.thenAccept(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
});

AsyncSupply 和 AsyncRun

再了解上面的逻辑原理之前,先看一下这么几个类:AsyncSupply、AsyncRun

图2 AsyncSupply

图3 AsyncRun

结合源码看一下,可以看到,AsyncSupply 和 AsyncRun都有两个构造参数dep 和 fn,dep都是一个CompletableFuture对象,fn则分别是Supplier 和 Runnable,分别对应我们调用supplyAsync 和runAsync时传入的函数接口

// AsyncSupply
static final class AsyncSupply<T> extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<T> dep; Supplier<? extends T> fn;
        AsyncSupply(CompletableFuture<T> dep, Supplier<? extends T> fn) {
            this.dep = dep; this.fn = fn;
        }

        public final Void getRawResult() { return null; }
        public final void setRawResult(Void v) {}
        public final boolean exec() { run(); return false; }

        public void run() {
            CompletableFuture<T> d; Supplier<? extends T> f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        d.completeValue(f.get()); // 这里是获取fn的执行结果,并设置为dep的结果
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete(); // 这里是执行后续操作,包括触发回调逻辑都在这里实现
            }
        }
    }
 
// AsyncRun
static final class AsyncRun extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<Void> dep; Runnable fn;
        AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
            this.dep = dep; this.fn = fn;
        }

        public final Void getRawResult() { return null; }
        public final void setRawResult(Void v) {}
        public final boolean exec() { run(); return false; }

        public void run() {
            CompletableFuture<Void> d; Runnable f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        f.run(); // 同理,因为runnalbe 无返回,所以直接调用f.run,执行线程中的任务,这里注意,谁调用f.run,那么谁的线程就会被阻塞,
                                 // 所以在单独使用线程的时候,我们都是使用start方法,而不至于阻塞主线程,这里f.run会阻塞,所以并不会拿不到执行结果就会去执行d.postComplete()
                        d.completeNull();
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete(); // 这里是执行后续操作,包括触发回调逻辑都在这里实现
            }
        }
    }

UniAccept 和 UniApply

我们再来看这两个类UniAccept 和 UniApply,这两个类都继承了UniCompletion extends Completion,这个不重要,只要知道它为其他子类提供了executor(线程池)、dep(CompletableFuture对象)、和src(自己this)三个构造函数即可,两个类也都有自己的fn,分别对应Consumer 和 Function函数,也即我们调用thenApply 和thenAccept方法传入的函数接口

// UniAccept
static final class UniAccept<T> extends UniCompletion<T,Void> {
        Consumer<? super T> fn;
        UniAccept(Executor executor, CompletableFuture<Void> dep,
                  CompletableFuture<T> src, Consumer<? super T> fn) {
            super(executor, dep, src); this.fn = fn; // 构造参数,着重了解dep src fn
        }
        final CompletableFuture<Void> tryFire(int mode) { //这里其实就是触发回调逻辑
            CompletableFuture<Void> d; CompletableFuture<T> a;
            Object r; Throwable x; Consumer<? super T> f;
            if ((d = dep) == null || (f = fn) == null
                || (a = src) == null || (r = a.result) == null)
                return null;
            tryComplete: if (d.result == null) {
                if (r instanceof AltResult) {
                    if ((x = ((AltResult)r).ex) != null) {
                        d.completeThrowable(x, r);
                        break tryComplete;
                    }
                    r = null;
                }
                try {
                    if (mode <= 0 && !claim())
                        return null;
                    else {
                        @SuppressWarnings("unchecked") T t = (T) r;
                        f.accept(t); // 在这里执行
                        d.completeNull();
                    }
                } catch (Throwable ex) {
                    d.completeThrowable(ex);
                }
            }
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);
        }
    }
// UniApply
static final class UniApply<T,V> extends UniCompletion<T,V> {
        Function<? super T,? extends V> fn;
        UniApply(Executor executor, CompletableFuture<V> dep,
                 CompletableFuture<T> src,
                 Function<? super T,? extends V> fn) {
            super(executor, dep, src); this.fn = fn;
        }
        // 回调触发逻辑
        final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> a;
            Object r; Throwable x; Function<? super T,? extends V> f;
            if ((d = dep) == null || (f = fn) == null
                || (a = src) == null || (r = a.result) == null)
                return null;
            tryComplete: if (d.result == null) {
                if (r instanceof AltResult) {
                    if ((x = ((AltResult)r).ex) != null) {
                        d.completeThrowable(x, r);
                        break tryComplete;
                    }
                    r = null;
                }
                try {
                    if (mode <= 0 && !claim())
                        return null;
                    else {
                        @SuppressWarnings("unchecked") T t = (T) r;
                        d.completeValue(f.apply(t)); // 这里才真正执行funciton.apply
                    }
                } catch (Throwable ex) {
                    d.completeThrowable(ex);
                }
            }
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);
        }
    }

// 父类 UniCompletion 提供的方法,使用定义的线程池来执行 dep的function,这也是thenApplyAsync等带有Async后缀的方法和普通方法的区别
final boolean claim() {
            Executor e = executor;
            if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
                if (e == null)
                    return true;
                executor = null; // disable
                e.execute(this);
            }
            return false;
        }

两个成员变量result & stack

为了接下来能理顺思路,我们再来看两个成员变量和一个方法

    volatile Object result;       // Either the result or boxed AltResult
    volatile Completion stack;    // Top of Treiber stack of dependent actions 注意,这里stack是Completion 也即上文的UniAccept/UniApply

		private static final VarHandle RESULT; // 关联CompletableFuture 的 result变量 存放自己的结果,例如future1.thenApply(fn2), 
                                           // 那么这里result其实存放的是futrue1的结果,要触发的fn2 被放置在stack中,也就是UniApply或者UniAccept等继承了Completion的对象
    private static final VarHandle STACK;  // 关联CompletableFuture 的 stack 变量 存放要触发的后续stage future 也就是UniApply或者UniAccept等继承了Completion的对象
    private static final VarHandle NEXT;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            RESULT = l.findVarHandle(CompletableFuture.class, "result", Object.class);
            STACK = l.findVarHandle(CompletableFuture.class, "stack", Completion.class);
            NEXT = l.findVarHandle(Completion.class, "next", Completion.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }

        // Reduce the risk of rare disastrous classloading in first call to
        // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
        Class<?> ensureLoaded = LockSupport.class;
    }
}

一个方法completeValue

// CAS设置result
final boolean completeValue(T t) {
       return RESULT.compareAndSet(this, null, (t == null) ? NIL : t);
}

执行原理

好了现在我们再来看示例代码,一步步来分析其执行原理

        // step1 创建一个CompletableFuture对象,并使用指定的线程池执行异步任务
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            // 在后台线程中执行耗时操作
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 42;
        });
        // step2 future2 依赖future1的结果,并且返回hello,thenApply的操作可以认为是往future1中注册回调,很多刚开始上手的同学,上来就接触lambda表达式,而忽略了带有accept和apply方法其实是
        // 实现了Consumer 和 Function函数,仅仅是应用场景不同,内部逻辑原理相似,同理supplyAsync方法其实是传入Supplier接口,实现其get()方法
        CompletableFuture<String> future2 = future1.thenApply(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return integer + "hello";
            }
        });
        // step3 同理,往future2中注册回调
        future2.thenAccept(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
step1

首先我们调用supplyAsync方法注册了一个异步任务,传参类型为Supplier函数匿名内部类实现,采用lambda表达式简化,我们把这个实现记为funciton1,即

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(function1);

接下来来看一下supplyAsync内部实现,future1 就是返回的d 也就是dep1

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
      return asyncSupplyStage(ASYNC_POOL, supplier); // ->asyncSupplyStage, supplier -> function1
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
                                                 Supplier<U> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<U> d = new CompletableFuture<U>(); // new 了一个CompletableFuture作为返回 记为dep1
        e.execute(new AsyncSupply<U>(d, f)); // 开启线程池执行,注意,这里就new了一个AsyncSupply对象,并且将 dep1 和 function1传入,将会直接执行AsyncSupply的run方法
        return d;
 }
// AsyncSupply RUN
public void run() {
  CompletableFuture<T> d; Supplier<? extends T> f;
  if ((d = dep) != null && (f = fn) != null) {
      dep = null; fn = null;
      if (d.result == null) {
        try {
          d.completeValue(f.get()); // 这里是获取fn的执行结果,并执行completeValue方法,CAS设置为dep1的结果, 
        } catch (Throwable ex) {
          d.completeThrowable(ex);
        }
      }
      d.postComplete(); // 这里是执行后续操作,包括触发回调逻辑都在这里实现,先不着急看其中的实现
    }
}
step2

接着,执行future1的thenApply方法,并且向其中传入Function匿名内部类,实现apply方法,我们将这个函数记为function2

CompletableFuture<String> future2 = future1.thenApply(function2)

下面一起看下thenApply的实现,可以看到thenApply 调用过程中要没直接设置已完成的值 -> uniApplyNow,要么new UniApply对象,将dep2即future2和function2封装,压入future1的stack中,一直等待future1的result不为空,因为thenApply方法没有线程池,所以,要么阻塞当前线程,要么阻塞回调线程,这也是为什么在一些调用异步IO场景中,尽量避免使用ThenApply的原因,因为诸如Netty等异步IO框架,其回调是在IO线程中做的,这样可能因后续处理有耗时逻辑,阻塞IO线程

public <U> CompletableFuture<U> thenApply(
    Function<? super T,? extends U> fn) {
    return uniApplyStage(null, fn); // 调用执行uniApplyStage,这里可以发现入参并没有线程池参数,这里注意一下,fn -> function2
}

private <V> CompletableFuture<V> uniApplyStage(
    Executor e, Function<? super T,? extends V> f) {
    if (f == null) throw new NullPointerException();
    Object r;
    if ((r = result) != null)  // 因为是future1调用 thenApply方法,而future1就是对应step1里面返回的dep1,所以这个result,就是dep1的结果,也就是当前future1的结果
    	return uniApplyNow(r, e, f); //如果future1的结果已经有值,证明已经完成,调用uniApplyNow
    CompletableFuture<V> d = newIncompleteFuture(); // 继续new 一个新的CompletableFuture对象,也就是dep2 -> future2
    unipush(new UniApply<T,V>(e, d, this, f)); // 如果future1 也就是dep1的结果result还未就绪,将dep2 和 funciton2传入UniApply
    return d; // 返回future2
}

// uniApplyNow方法
private <V> CompletableFuture<V> uniApplyNow(
        Object r, Executor e, Function<? super T,? extends V> f) {
        Throwable x;
        CompletableFuture<V> d = newIncompleteFuture();
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                d.result = encodeThrowable(x, r);
                return d;
            }
            r = null;
        }
        try {
            if (e != null) {
                e.execute(new UniApply<T,V>(null, d, this, f)); //如果线程池不为null,会将任务丢到新线程池,但是thenApply方法的线程池为null
            } else {
                @SuppressWarnings("unchecked") T t = (T) r;
                d.result = d.encodeValue(f.apply(t)); // thenApply方法会执行当前逻辑,阻塞当前的线程,理解为,如果future1的结果已经有值,则该方法是在当前线程执行,会阻塞当前线程
            }
        } catch (Throwable ex) {
            d.result = encodeThrowable(ex);
        }
        return d;
}

// 看unipush方法
final void unipush(Completion c) {
        if (c != null) {
            while (!tryPushStack(c)) { // 这里将future2 也就是dep2 和 funciton2封装为c 放到this.stack中 也就是future1的stack中
                if (result != null) { // 因为当前的future还是future1,future2已经封装到c里面了,这里会一直等dep1的结果是否设置完成
                    NEXT.set(c, null); 
                    break;
                }
            }
            if (result != null)
                c.tryFire(SYNC); //如果结果已经有值,触发c中的function2逻辑,并将结果封装到dep2 -> future2中,理论上这里仍旧会阻塞当前线程
                                 // 但这里建议结合最上面CompletableFuture.supplyAsync方法的回调逻辑来看,如果此时结果不为空,也有可能在future1的回调中就执行了,
                                 // 所以阻塞的是回调线程
                                 // -> d.postComplete()
        }
}

// supplyAsync -> AsyncSupply.run方法 回调 -> postComplete
final void postComplete() {
        /*
         * On each step, variable f holds current dependents to pop
         * and run.  It is extended along only one path at a time,
         * pushing others to avoid unbounded recursion.
         */
        CompletableFuture<?> f = this; Completion h;
        while ((h = f.stack) != null ||
               (f != this && (h = (f = this).stack) != null)) {
            CompletableFuture<?> d; Completion t;
            if (STACK.compareAndSet(f, h, t = h.next)) {
                if (t != null) {
                    if (f != this) {
                        pushStack(h);
                        continue;
                    }
                    NEXT.compareAndSet(h, t, null); // try to detach
                }
                f = (d = h.tryFire(NESTED)) == null ? this : d; // 实际上,回调时,已经会触发tryFire,但是不用担心回调这里会执行,上面c.tryFire会重复执行,
                																								// 内部逻辑保证每个任务仅会执行一次
            }
        }
 }

// UniApply类中的回调触发逻辑 
final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> a;
            Object r; Throwable x; Function<? super T,? extends V> f; // 这里其实就是d -> dep2 和 f -> function2
            if ((d = dep) == null || (f = fn) == null
                || (a = src) == null || (r = a.result) == null)
                return null;  // 这里的a.result,a就是src,而src的入参是this也就是future1, 不要和future2 -> dep2的结果搞混
            tryComplete: if (d.result == null) { // 这里就保证dep2如果已经有结果(被回调执行完成),就不会继续执行,所以任务只能执行一次
                if (r instanceof AltResult) {
                    if ((x = ((AltResult)r).ex) != null) {
                        d.completeThrowable(x, r);
                        break tryComplete;
                    }
                    r = null;
                }
                try {
                    if (mode <= 0 && !claim())       // 如果线程池,不为null, claim中会将当前的this -> UniApply 丢到线程池执行,这也就是和Async后缀的方法区别的原因
                        return null;
                    else {
                        @SuppressWarnings("unchecked") T t = (T) r;
                        d.completeValue(f.apply(t)); // 真正的执行function2,所以可以看出,因为thenApply 线程池参数为null,所以这里是回调线程在执行,会阻塞回调线程
                    }
                } catch (Throwable ex) {
                    d.completeThrowable(ex);
                }
            }
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);  // 继续触发后续依赖
        }
step3

step3和step2基本原理是一致的,只不过thenAccept方法调用时,入参为UniAccept,是为无参数返回

下面简单看下

future2.thenAccept(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        })
// -> 我们设thenAccept内部入参为function3
// -> uniAcceptStage 线程池依然为null
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
      return uniAcceptStage(null, action);
}

// 执行uniAcceptNow,或者new UniAccept压栈
private CompletableFuture<Void> uniAcceptStage(Executor e,
                                                   Consumer<? super T> f) {
        if (f == null) throw new NullPointerException();
        Object r;
        if ((r = result) != null)
            return uniAcceptNow(r, e, f);
        CompletableFuture<Void> d = newIncompleteFuture();
        unipush(new UniAccept<T>(e, d, this, f)); // 将dep3 和 function3压入future2的stack中,等待被触发
        return d;
}

// uniAcceptNow
private CompletableFuture<Void> uniAcceptNow(
        Object r, Executor e, Consumer<? super T> f) {
        Throwable x;
        CompletableFuture<Void> d = newIncompleteFuture();
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                d.result = encodeThrowable(x, r);
                return d;
            }
            r = null;
        }
        try {
            if (e != null) {
                e.execute(new UniAccept<T>(null, d, this, f)); // 线程池为null
            } else {
                @SuppressWarnings("unchecked") T t = (T) r;
                f.accept(t);   // 执行function3
                d.result = NIL;
            }
        } catch (Throwable ex) {
            d.result = encodeThrowable(ex);
        }
        return d; // 返回
 }

UniAccept 这里就不分析了,因为原理和UniApply相同,只是没有返回值

最后贴一下thenApplyAsync的实现,区别仅仅在于线程池的使用

  public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn, Executor executor) {
        return uniApplyStage(screenExecutor(executor), fn); // 依旧调用uniApplyStage,只不过线程池不为null
  }

项目中的异步应该如何写

下面我以几个代码为例,看下项目中异步代码会有什么问题

首先我们声明一个IO接口类,它提供一个异步处理processAsync方法,接下来可以写两个实现类分别为AbstractIoAsyncImplV1和AbstractIoAsyncImplV2,他们都提供抽象的doProcess方法,用于底层数据处理。

public interface IoProcessor<T> {

    CompletableFuture<T> processAsync(Request request);
}

AbstractIoAsyncImplV1

AbstractIoAsyncImplV1示例如下代码:

@Slf4j
public abstract class AbstractIoAsyncImplV1<T> implements IoProcessor<T> {
    static ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("self-pool-%d").build();

    public abstract CompletableFuture<T> doProcess(Request request);
    @Override
    public CompletableFuture<T> processAsync(Request request) {
        log.info("start processAsync"); //一直到这里,都是没有开启任何额外的线程池,也就是说它依赖外部线程池,在项目中可以是架构的调度线程池,也可以是IO线程池
        return doProcess(request);
    }
}

可以看出,AbstractIoAsyncImplV1要想实现异步,全都依赖doProcess方法的实现,至少到doProcess,其执行的线程都是依赖外部线程池,可以是调度线程池,也可以是IO线程池

下面我们看一个例子 UserBehaviorIoProcessor类,这里我底层模拟了一个异步RPC调用

@Slf4j
public class UserBehaviorIoProcessor extends AbstractIoAsyncImplV1<String> {
    @Override
    public CompletableFuture<String> doProcess(Request request) {
        log.info("ready to rpc...");
        CompletableFuture<String> rpcAsyncFuture = getRpcAsyncFuture(request);
        return rpcAsyncFuture.thenApply(r -> {
            log.info("after rpc start process result...");
            return r + "uuid";
        }).exceptionally(e -> null);
    }

    private static CompletableFuture<String> getRpcAsyncFuture(Request request) {
        // 模拟异步rpc
        return RpcUtils.asyncRpcGet(request);
    }
}

如果这个rpc是异步的,那将不会有任何问题,因为底层执行rpc和回调的线程池,应该是内部的线程池,如下,它不会阻塞执行doProcess方法的线程:

// 模拟异步rpc
public static CompletableFuture<String> asyncRpcGet(Request request) {
        // 这里使用ForkJoinPool 线程池模拟底层rpc调用等待的线程池
        return CompletableFuture.supplyAsync(() -> rpcGet(request))
                .orTimeout(5000, TimeUnit.MILLISECONDS);
    }

// 模拟同步rpc, 同步等待3s
public static String rpcGet(Request request) {
        try {
            log.info("rpcGet....");
            Thread.sleep(3000);
            log.info("rpcGet end!!!!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "JIA Behavior...";
    }

再来看另一个类,这个可能也是我们开发过程中不注意的,调用了同步rpc接口,可以预见的是,在这里由于同步rpc没有开启任何额外的线程来处理结果,那么它会直接阻塞调用doProcess的线程,也就是外部的线程,那么对于一个项目来说,如果调度线程或者IO线程都这样被阻塞在这里,吞吐必定上不去

@Slf4j
public class UserBehaviorSyncIoProcessor extends AbstractIoAsyncImplV1<String> {
    @Override
    public CompletableFuture<String> doProcess(Request request) {
        // 模拟同步调用
        CompletableFuture<String> rpcFuture = getRpcFuture(request);
        return rpcFuture.thenApply(r -> {
            // 可以思考一下,如果这里有额外耗时的操作会怎么样?答:阻塞当前回调线程,那如果你执行的rpc是同步的,就会阻塞当前的线程
            log.info("After rpc process result start...");
            return r + "uuid";
        }).exceptionally(e -> null);
    }

    private static CompletableFuture<String> getRpcFuture(Request request) {
        CompletableFuture<String> rpcFuture = new CompletableFuture<>();
        // 调用rpc结果
        String result = RpcUtils.rpcGet(request); // 这里会同步等待3s
        try {
            rpcFuture.complete(result);
            log.info("rpc success!");
        } catch (Exception e) {
            log.error("error!", e);
            rpcFuture.completeExceptionally(e);
        }
        return rpcFuture;
    }
}

AbstractIoAsyncImplV2

接下来,看下AbstractIoAsyncImplV2的实现:

可以看到,v2与v1的区别在于,采用独立于外部的线程池,这样就不会阻塞调度线程,当外部调用processAsync 发送请求时,后续的逻辑都交由内部来处理,而内部线程数据就绪后只需要执行一下回调方法将结果返回即可

@Slf4j
public abstract class AbstractIoAsyncImplV2<T> implements IoProcessor<T> {
    static ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("self-pool-%d").build();
    // 独立线程池用于处理任务
    private static final ExecutorService SON_EXECUTOR = new ThreadPoolExecutor(
            3,
            3,
            100L,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), guavaThreadFactory);
    // 带有回调函数,可以一路往下传
    public abstract void doProcessAsync(Request request, BiConsumer<T, Throwable> biConsumer);
    @Override
    public CompletableFuture<T> processAsync(Request request) {
        log.info("SON_EXECUTOR :" + SON_EXECUTOR);
        log.info("Thread name processAsync is: " + Thread.currentThread().getName());
        CompletableFuture<T> resultFuture = new CompletableFuture<>();
        SON_EXECUTOR.submit(() -> doProcessAsync(request, (t, ex) -> {
            // 自定义回调函数,结果会封装在t中,一直往下传,理论上每一步都可以继续往下传回调函数
            if (ex != null) {
                log.error("error!", ex);
                resultFuture.completeExceptionally(ex);
            }
            // 返回已经ready的结果,这一步由内部调用底层rpc的线程来完成,也就是回调线程
            resultFuture.complete(t);
            log.info("biConsumer accept result:" + t);
        }));
        return resultFuture;
    }
}
@Slf4j
public class UserInfoFetchProcessor extends AbstractIoAsyncImplV2<String> {

    public void doProcess(Request request, ResultHandler<String> handler) {
        // TODO
    }
    
    // 继承AbstractIoAsyncImplV2,定义结果处理的ResultHandler,将回调封装
    @Override
    public void doProcessAsync(Request request, BiConsumer<String, Throwable> biConsumer) {
        ResultHandler<String> handler = new ResultHandler<>(biConsumer);
        try {
            doProcess(request, handler);
            log.info("success!");
        } catch (Exception e) {
            handler.onFailure(e);
            log.error("failed!");
        }
    }

}


// 具体的实现类,调用异步rpc,注册回调
@Slf4j
public class UserBehaviorAsyncIoProcessor extends UserInfoFetchProcessor {

    @Override
    public void doProcess(Request request, ResultHandler<String> handler) {
        RpcUtils.asyncRpcGet(request).thenAccept(result -> {
            log.info("rpc success! RESULT PROCESS...");
            // 回调逻辑
            handler.onSuccess(result + "uuid");
        });
    }
}

// 处理结果,回调函数封装的类
@Slf4j
public class ResultHandler<T> {

    public ResultHandler() {

    }

    public ResultHandler(BiConsumer<T, Throwable> callback) {
        this.callback = callback;
    }

    /**
     *  用于设置回调函数的方法.
     */
    private BiConsumer<T, Throwable> callback;

    /**
     * 成功的情况
     * @param t 返回结果
     */
    public void onSuccess(T t) {
        if (t == null) {
            log.info("result is null!!!");
        }
        log.info("Thread name ResultHandler is: " + Thread.currentThread().getName()); // 回调处理在这里下面一行代码,两边可以输出一些业务日志等
        // callback
        callback.accept(t, null);
        // monitor
    }

    /**
     * 处理失败情况.
     * @param e 捕获的异常
     */
    public void onFailure(Exception e) {
        callback.accept(null, e);
        log.error("Error occurred: " + e.getMessage(), e);
    }
}

看上面调用异步RPC时,异步rpc内部是又开了一个ForkJoinPool线程的,所以理论上如果底层全部支持异步,那么执行doProcessAsync的线程也可以得到解放,可以继续转发其他的请求

这里我也同样定义了一个调用同步Rpc接口的类,用于最终的测试阶段

@Slf4j
public class UserBehaviorAsyncIoProcessorV2 extends UserInfoFetchProcessor {

    @Override
    public void doProcess(Request request, ResultHandler<String> handler) {
        try {
            log.info("sync rpc ready....");
            String str = RpcUtils.rpcGet(request); // 同步等待3s
            log.info("result processing....");
            handler.onSuccess(str + "uuid");       // 执行回调,这里阻塞的是执行doProcessAsync的线程,也不会阻塞最外边的调度线程
        } catch (Exception e) {
            handler.onFailure(e);
        }
    }
}

测试异步调用

看下最终的测试代码,首先是AbstractIoAsyncImplV1:

@Test
    public void testBehaviorSyncIoProcessor() throws ExecutionException, InterruptedException {
        Request request = new Request();
        request.setParam((JsonObject) JsonParser.parseString("{num:2}"));
        request.setUrl("http://www.baidu.com");
        long start = System.currentTimeMillis();
        // 模拟调度线程池
        ExecutorService executor = new ThreadPoolExecutor(
                2,
                2,
                100L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>());
        
        // 提交两个调用同步rpc的任务
        // 同步任务1
        executor.submit(() -> {
            behaviorSyncIoProcessor.processAsync(request).thenAccept(r -> {
                log.info("sync1 test result is : " + r + "...sync");
                log.info("sync1 time is :" + (System.currentTimeMillis() - start) + "ms");
            });
        });
        // 同步任务2
        executor.submit(() -> {
            behaviorSyncIoProcessor.processAsync(request).thenAccept(r -> {
                log.info("sync2 test result is : " + r + "...sync");
                log.info("sync2 time is :" + (System.currentTimeMillis() - start) + "ms");
            });
        });

        // 异步任务
        executor.submit(() -> {
            behaviorIoProcessor
                    .processAsync(request).thenAccept(r -> {
                        log.info("final test result is : " + r);
                        log.info("take time:" + (System.currentTimeMillis() - start) + "ms");
                    });
        });

        log.info("do other things...");
        Thread.sleep(7000);
    }

最终控制台结果如下:

[pool-3-thread-2] INFO  c.j.f.b.UserBehaviorIoProcessorTest - sync2 time is :3020ms
[pool-3-thread-1] INFO  c.j.f.b.UserBehaviorIoProcessorTest - sync1 time is :3021ms
[pool-3-thread-1] INFO  c.j.f.b.UserBehaviorIoProcessor - ready to rpc...
[ForkJoinPool.commonPool-worker-19] INFO  c.j.f.b.UserBehaviorIoProcessorTest - take time:6031ms

再来测试AbstractIoAsyncImplV2:

@Test
public void testBehaviorAsyncIoProcessor() throws InterruptedException {
        Request request = new Request();
        request.setParam((JsonObject) JsonParser.parseString("{num:2}"));
        request.setUrl("http://www.baidu.com");
        long start = System.currentTimeMillis();
        // 模拟调度线程池
        ExecutorService executor = new ThreadPoolExecutor(
                2,
                2,
                100L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>());
        // 同步任务1
        executor.submit(() -> {
            behaviorAsyncIoProcessorV2.processAsync(request).thenAccept(r -> {
                log.info("sync1 result is: " + r + "...v2");
                log.info("sync1 time is :" + (System.currentTimeMillis() - start) + "ms");
            });
        });
        // 同步任务2
        executor.submit(() -> {
            behaviorAsyncIoProcessorV2.processAsync(request).thenAccept(r -> {
                log.info("sync2 result is: " + r + "...v2");
                log.info("sync2 time is :" + (System.currentTimeMillis() - start) + "ms");
            });
        });
        // 异步任务1
        executor.submit(() -> {
            behaviorAsyncIoProcessor.processAsync(request).thenAccept(r -> {
                log.info("final test result is : " + r);
                log.info("take time:" + (System.currentTimeMillis() - start) + "ms");
            });
        });

        log.info("do other things...");
        Thread.sleep(5000);
    }

控制台输出如下:

[ForkJoinPool.commonPool-worker-19] INFO  c.j.f.b.UserBehaviorIoProcessorTest - take time:3023ms
[self-pool-0] INFO  c.j.f.b.UserBehaviorIoProcessorTest - sync1 time is :3027ms
[self-pool-1] INFO  c.j.f.b.UserBehaviorIoProcessorTest - sync2 time is :3027ms

参考:

CompletableFuture原理与实践-外卖商家端API的异步化 - 美团技术团队

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值