• JUC下的异步编程工具使用详情以及源码分析(FutureTask、CompletableFuture)


    异步编程

    一、FutureTask应用&源码分析

    1.1 FutureTask介绍

    FutureTask是一个可以取消异步任务的类。FutureTaskFuture做的一个基本实现。可以调用方法区开始和取消一个任务

    一般是配合Callable去使用

    异步任务启动之后,可以获取一个绑定当前异步任务的FutureTask

    可以基于FutureTask的方法去取消任务,查看任务是否结果,以及获取任务的返回结果

    FutureTask内部的整体结构中,实现了RunnableFuture的接口,这个接口又继承了RunnableFuture这个两个接口。所以FutureTask也可以作为任务直接交给线程池去处理

    1.2 FutureTask应用

    大方向是FutureTask对任务的控制:

    • 任务执行过程中状态的控制
    • 任务执行完毕后,返回结果的获取

    FutureTask的任务在执行run方法后,是无法被再次运行,需要使用runAndReset方法才可以

    public static void main(String[] args) throws InterruptedException {
        // 构建FutureTask,基于泛型执行返回结果类型
        // 在有参构造中,声明Callable或者Runnable指定任务
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println("任务开始执行……");
            Thread.sleep(2000);
            System.out.println("任务执行完毕……");
            return "OK!";
        });
    
        // 构建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
    
        // 线程池执行任务
        service.execute(futureTask);
    
        // futureTask提供了run方法,一般不会自己去调用run方法,让线程池去执行任务,由线程池去执行run方法
        // run方法在执行时,是有任务状态的。任务已经执行了,再次调用run方法无效的。
        // 如果希望任务可以反复被执行,需要去调用runAndReset方法
    //        futureTask.run();
    
        // 对返回结果的获取,类似阻塞队列的poll方法
        // 如果在指定时间内,没有拿到方法的返回结果,直接扔TimeoutException
    //        try {
    //            String s = futureTask.get(3000, TimeUnit.MILLISECONDS);
    //            System.out.println("返回结果:" + s);
    //        } catch (Exception e) {
    //            System.out.println("异常返回:" + e.getMessage());
    //            e.printStackTrace();
    //        }
    
        // 对返回结果的获取,类似阻塞队列的take方法,死等结果
    //        try {
    //            String s = futureTask.get();
    //            System.out.println("任务结果:" + s);
    //        } catch (ExecutionException e) {
    //            e.printStackTrace();
    //        }
    
        // 对任务状态的控制
    //        System.out.println("任务结束了么?:" + futureTask.isDone());
    //        Thread.sleep(1000);
    //        System.out.println("任务结束了么?:" + futureTask.isDone());
    //        Thread.sleep(1000);
    //        System.out.println("任务结束了么?:" + futureTask.isDone());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    1.3 FutureTask源码分析

    FutureTask的源码,要从几个方向去看:

    • 先查看FutureTask中提供的一些状态
    • 再查看任务的执行过程
    1.3.1 FutureTask中的核心属性

    清楚任务的流转流转状态是怎样的,其次对于核心属性要追到是干嘛的

    /**
     FutureTask的核心属性
     FutureTask任务的状态流转
     * NEW -> COMPLETING -> NORMAL           任务正常执行,并且返回结果也正常返回
     * NEW -> COMPLETING -> EXCEPTIONAL      任务正常执行,但是结果是异常(执行途中出现异常)
     * NEW -> CANCELLED                      任务被取消   
     * NEW -> INTERRUPTING -> INTERRUPTED    任务被中断
     */
    // 记录任务的状态
    private volatile int state;
    // 任务被构建之后的初始状态
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
    
    /** 需要执行任务,会被赋值到这个属性 */
    private Callable<V> callable;
    /** 任务的任务结果要存储在这几个属性中 */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** 执行任务的线程 */
    private volatile Thread runner;
    /** 等待返回结果的线程Node对象, */
    private volatile WaitNode waiters;
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1.3.2 FutureTask的run方法

    任务执行前的一些判断,以及调用任务封装结果的方式,还有最后的一些后续处理

    // 当线程池执行FutureTask任务时,会调用的方法
    public void run() {
        // 如果当前任务状态不是NEW,直接return告辞
        if (state != NEW ||  
            // 如果状态正确是NEW,这边需要基于CAS将runner属性设置为当前线程
            // 如果CAS失败,直接return告辞
            !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
            return;
    
        try {
            // 将要执行的任务拿到
            Callable<V> c = callable;
            // 健壮性判断,保证任务不是null
            // 再次判断任务的状态是NEW(DCL)
            if (c != null && state == NEW) {
                // 执行任务
                // result:任务的返回结果
                // ran:如果为true,任务正常结束。 如果为false,任务异常结束。
                V result;
                boolean ran;
                try {
                    // 执行任务
                    result = c.call();
                    // 正常结果,ran设置为true
                    ran = true;
                } catch (Throwable ex) {
                    // 如果任务执行期间出了异常
                    // 返回结果置位null
                    result = null;
                    // ran设置为false
                    ran = false;
                    // 封装异常结果
                    setException(ex);
                }
                if (ran)
                    // 封装正常结果
                    set(result);
            }
        } finally {
            // 将执行任务的线程置位null
            runner = null;
            // 拿到任务的状态
            int s = state;
            // 如果状态大于等于INTERRUPTING
            if (s >= INTERRUPTING)
                // 进来代表任务中断,做一些后续处理
                handlePossibleCancellationInterrupt(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    1.3.3 FutureTask的set&setException方法

    任务执行完毕后,修改任务的状态以及封装任务的结果

    // 没有异常的时候,正常返回结果
    protected void set(V v) {
        // 因为任务执行完毕,需要将任务的状态从NEW,修改为COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 将返回结果赋值给 outcome 属性
            outcome = v;
            // 将任务状态变为NORMAL,正常结束
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
            // 一会再说……
            finishCompletion();
        }
    }
    
    // 任务执行期间出现了异常,这边要封装结果
    protected void setException(Throwable t) {
        // 因为任务执行完毕,需要将任务的状态从NEW,修改为COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 将异常信息封装到 outcome 属性
            outcome = t;
            // 将任务状态变为EXCEPTIONAL,异常结束
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); 
            // 一会再说……
            finishCompletion();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    1.3.4 FutureTask的cancel方法

    任务取消的一个方式

    • 任务直接从NEW状态转换为CANCEL
    • 任务从NEW状态变成INTERRUPTING,然后再转换为INTERRUPTED
    // 取消任务操作
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 查看任务的状态是否是NEW,如果NEW状态,就基于传入的参数mayInterruptIfRunning
        // 决定任务是直接从NEW转换为CANCEL,还是从NEW转换为INTERRUPTING
        if (!(state == NEW && 
            UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {   
            // 如果mayInterruptIfRunning为true
            // 就需要中断线程
            if (mayInterruptIfRunning) {
                try {
                    // 拿到任务线程
                    Thread t = runner;
                    if (t != null)
                        // 如果线程不为null,直接interrupt
                        t.interrupt();
                } finally { 
                    // 将任务状态设置为INTERRUPTED
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            // 任务结束后的一些处理~~ 一会看~~
            finishCompletion();
        }
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    测试

    public static void main(String[] args) throws InterruptedException, ExecutionException {
     // 构建FutureTask,基于泛型执行返回结果类型
     // 在有参构造中,声明Callable或者Runnable指定任务
     FutureTask<String> futureTask = new FutureTask<>(() -> {
         System.out.println("任务开始执行……");
         Thread.sleep(2000);
         System.out.println("任务执行完毕……");
         return "OK!";
     });
     // 构建线程池
     ExecutorService service = Executors.newFixedThreadPool(10);
    
     // 线程池执行任务
     service.execute(futureTask);
     Thread.sleep(1000);
     System.out.println(futureTask.cancel(true));
     System.out.println(futureTask.get());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    console

    任务开始执行……
    true
    Exception in thread "main" java.util.concurrent.CancellationException
    	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
    	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    	at com.armin.thread.tool.FutureTaskTest.main(FutureTaskTest.java:26)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • futureTask.cancel(true):会中断执行的程序
    • futureTask.cancel(false):不会中断执行的程序,任务会执行完,但get()同样会抛出CancellationException异常

    如果在等待线程的任务执行完毕后再调用cancel()返回falseget()同样也会拿到返回值与正常情况无异

    1.3.5 FutureTask的get方法

    这个是线程获取FutureTask任务执行结果的方法

    // 拿任务结果
    public V get() throws InterruptedException, ExecutionException {
        // 获取任务的状态
        int s = state;
        // 要么是NEW,任务还没执行完
        // 要么COMPLETING,任务执行完了,结果还没封装好。
        if (s <= COMPLETING)
            // 让当前线程阻塞,等待结果
            s = awaitDone(false, 0L);
        // 最终想要获取结果,需要执行report方法
        return report(s);
    }
    
    // 线程等待FutureTask结果的过程
    private int awaitDone(boolean timed, long nanos) throws InterruptedException {
        // 针对get方法传入了等待时长时,需要计算等到什么时间点
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        // 声明好需要的Node,queued:放到链表中了么?
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            // 查看线程是否中断,如果中断,从等待链表中移除,甩个异常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            // 拿到状态
            int s = state;
            // 到这,说明任务结束了。
            if (s > COMPLETING) {
                if (q != null)
                    // 如果之前封装了WaitNode,现在要清空
                    q.thread = null;
                return s;
            }
            // 如果任务状态是COMPLETING,这就不需要去阻塞线程,让步一下,等待一小会,结果就有了
            else if (s == COMPLETING) 
                Thread.yield();
            // 如果还没初始化WaitNode,初始化
            else if (q == null)
                q = new WaitNode();
            // 没放队列的话,直接放到waiters的前面
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            // 准备挂起线程,如果timed为true,挂起一段时间
            else if (timed) {
                // 计算出最多可以等待多久
                nanos = deadline - System.nanoTime();
                // 如果等待的时间没了
                if (nanos <= 0L) {
                    // 移除当前的Node,返回任务状态
                    removeWaiter(q);
                    return state;
                }
                // 等一会
                LockSupport.parkNanos(this, nanos);
            }
            else
                // 死等
                LockSupport.park(this);
        }
    }
    
    // get的线程已经可以阻塞结束了,基于状态查看能否拿到返回结果
    private V report(int s) throws ExecutionException {
        // 拿到outcome 返回结果
        Object x = outcome;
        // 如果任务状态是NORMAL,任务正常结束,返回结果
        if (s == NORMAL)
            return (V)x;
        // 如果任务状态大于等于取消
        if (s >= CANCELLED)
            // 直接抛出异常
            throw new CancellationException();
        // 到这就是异常结束
        throw new ExecutionException((Throwable)x);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    1.3.6 FutureTask的finishCompletion方法

    只要任务结束了,无论是正常返回,异常返回,还是任务被取消都会执行这个方法

    而这个方法其实就是唤醒那些执行get方法等待任务结果的线程

    // 任务结束后触发
    private void finishCompletion() {
        // 在任务结束后,需要唤醒
        for (WaitNode q; (q = waiters) != null;) {
            // 第一步直接以CAS的方式将WaitNode置为null
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    // 拿到了Node中的线程
                    Thread t = q.thread;
                    // 如果线程不为null
                    if (t != null) {
                        // 第一步先置位null
                        q.thread = null;
                        // 直接唤醒这个线程
                        LockSupport.unpark(t);
                    }
                    // 拿到当前Node的next
                    WaitNode next = q.next;
                    // next为null,代表已经将全部节点唤醒了吗,跳出循环
                    if (next == null)
                        break;
                    // 将next置位null
                    q.next = null; 
                    // q的引用指向next
                    q = next;
                }
                break;
            }
        }
    
        // 任务结束后,可以基于这个扩展方法,记录一些信息
        done();
    
        // 任务执行完,把callable具体任务置位null
        callable = null;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    扩展口

    可以继承FutureTask重写done()方法实现线程在执行完毕后(不论正常还是异常执行结束)的一系列操作

    二、CompletableFuture应用&源码分析

    2.1 CompletableFuture介绍

    平时多线程开发一般就是使用RunnableCallableThreadFutureTaskThreadPoolExecutor这些内容和并发编程息息相关。相对来对来说成本都不高,多多使用是可以熟悉这些内容。这些内容组合在一起去解决一些并发编程的问题时,很多时候没有办法很方便的去完成异步编程的操作

    Thread + Runnable:执行异步任务,但是没有返回结果

    Thread + Callable + FutureTask:完整一个可以有返回结果的异步任务

    • 获取返回结果,如果基于get方法获取,线程需要挂起在WaitNode
    • 获取返回结果,也可以基于isDone判断任务的状态,但是这里需要不断轮询

    上述的方式都是有一定的局限性的。

    比如说任务A,任务B,还有任务C。其中任务B还有任务C执行的前提是任务A先完成,再执行任务B和任务C

    如果任务的执行方式逻辑比较复杂,可能需要业务线程导出阻塞等待,或者是大量的任务线程去编一些任务执行的业务逻辑。对开发成本来说比较高

    CompletableFuture就是帮你处理这些任务之间的逻辑关系,编排好任务的执行方式后,任务会按照规划好的方式一步一步执行,不需要让业务线程去频繁的等待

    2.2 CompletableFuture应用

    CompletableFuture应用还是需要一内内的成本的

    首先对CompletableFuture提供的函数式编程中三个函数有一个掌握

    • Supplier:生产者,没有入参,有返回结果
    • Consumer:消费者,有入参,但是没有返回结果
    • Function:函数,有入参,又有返回结果
    2.2.1 supplyAsync

    CompletableFuture如果不提供线程池的话,默认使用的ForkJoinPool,而ForkJoinPool内部是守护线程,如果main线程结束了,守护线程会跟着一起结束

    public static void main(String[] args)  {
        // 生产者,可以指定返回结果
        CompletableFuture<String> firstTask = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务开始执行");
            System.out.println("异步任务执行结束");
            return "返回结果";
        });
    
        String result1 = firstTask.join();
        String result2 = null;
        try {
            result2 = firstTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    
        System.out.println(result1 + "," + result2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    console

    异步任务开始执行
    异步任务执行结束
    返回结果,返回结果
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5

    调用completeFuture.get()方法会导致阻塞,直到任务执行结束

    也可以使用非阻塞的方法,比如while循环调用isDone()方法为判断任务是否执行完毕,如果没有执行完毕可以去做别的事情

    2.2.2 runAsync

    当前方式既不会接收参数,也不会返回任何结果,非常基础的任务编排方式

    public static void main(String[] args) throws IOException {
        CompletableFuture.runAsync(() -> {
            System.out.println("任务go");
            System.out.println("任务done");
        });
    
        System.in.read();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    console

    任务go
    任务done
    
    • 1
    • 2
    2.2.3 thenApply,thenApplyAsync

    有任务A,还有任务B

    任务B需要在任务A执行完毕后再执行

    而且任务B需要任务A的返回结果

    任务B自身也有返回结果

    thenApply可以拼接异步任务,前置任务处理完之后,将返回结果交给后置任务,然后后置任务再执行(使用的同一个线程执行)

    thenApply提供了带有Async的方法,可以指定每个任务使用的具体线程池

    public static void main(String[] args) throws IOException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
    
        /*CompletableFuture taskA = CompletableFuture.supplyAsync(() -> {
            String id = UUID.randomUUID().toString();
            System.out.println("执行任务A:" + id);
            return id;
        });
        CompletableFuture taskB = taskA.thenApply(result -> {
            System.out.println("任务B获取到任务A结果:" + result);
            result = result.replace("-", "");
            return result;
        });
    
        System.out.println("main线程拿到结果:" + taskB.join());*/
    
        CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> {
            String id = UUID.randomUUID().toString();
            System.out.println("执行任务A:" + id + "," + Thread.currentThread().getName());
            return id;
        }).thenApplyAsync(result -> {
            System.out.println("任务B获取到任务A结果:" + result + "," + Thread.currentThread().getName());
            result = result.replace("-", "");
            return result;
        },executor);
    
        System.out.println("main线程拿到结果:" + taskB.join());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    2.2.4 thenAccept,thenAcceptAsync

    套路和thenApply一样,都是任务A和任务B的拼接

    前置任务需要有返回结果,后置任务会接收前置任务的结果,返回后置任务没有返回值

    public static void main(String[] args) throws IOException {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A");
            return "abcdefg";
        }).thenAccept(result -> {
            System.out.println("任务b,拿到结果处理:" + result);
        });
    
        System.in.read();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2.2.5 thenRun,thenRunAsync

    套路和thenApplythenAccept一样,都是任务A和任务B的拼接

    前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也会有返回结果

    public static void main(String[] args) throws IOException {
        CompletableFuture.runAsync(() -> {
            System.out.println("任务A!!");
        }).thenRun(() -> {
            System.out.println("任务B!!");
        });
      
        System.in.read();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    thenRunAsyncthenAccept一致,只是说可以传线程池进行执行,如果不传则使用默认线程池

    private static final Executor asyncPool = useCommonPool ?
         ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    
    • 1
    • 2
    2.2.6 thenCombine,thenAcceptBoth,runAfterBoth

    比如有任务A,任务B,任务C。任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C

    A+B ------ C

    基于前面thenApplythenAcceptthenRun知道了一般情况三种任务的概念

    thenCombine以及thenAcceptBoth还有runAfterBoth的区别是一样的

    public static void main(String[] args) throws IOException {
    	long start = System.currentTimeMillis();
        CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程A:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 50;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println("线程B:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 49;
        }), (resA, resB) -> {
            System.out.println("线程C:" + Thread.currentThread().getName());
            return resA + resB;
        });
        System.out.println(taskC.join());
        System.out.println("执行时间:" + (System.currentTimeMillis() - start) + "ms");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    console

    线程A:ForkJoinPool.commonPool-worker-9
    线程B:ForkJoinPool.commonPool-worker-2
    线程C:ForkJoinPool.commonPool-worker-9
    99
    执行时间:2039ms
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.2.7 applyToEither,acceptEither,runAfterEither

    比如有任务A,任务B,任务C。任务A和任务B并行执行,只要任务A或者任务B执行任一任务执行完毕,就会开始执行任务C

    A or B ----- C

    applyToEitheracceptEitherrunAfterEither三个方法拼接任务的方式都是一样的

    区别

    • applyToEither:可以接收结果并且返回结果
    • acceptEither:可以接收结果没有返回结果
    • runAfterEither:不接收结果也没返回结果
    public static void main(String[] args) throws IOException {
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A, " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 88;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B, " + Thread.currentThread().getName());
            return 77;
        }), resultFirst -> {
            System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
            return resultFirst;
        });
        System.out.println(task.join());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    console

    任务A, ForkJoinPool.commonPool-worker-9
    任务B, ForkJoinPool.commonPool-worker-2
    任务C,获取到值:77 main
    77
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.2.8 exceptionally,thenCompose,handle
    • exceptionally:这个也是拼接任务的方式,但是只有前面业务执行时出现异常了,才会执行当前方法来处理只有异常出现时,CompletableFuture的编排任务没有处理完时,才会触发

      public static void main(String[] args) throws IOException {
      	CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
              System.out.println("任务A, " + Thread.currentThread().getName());
              int i = 1/0;
              return 88;
          }).applyToEither(CompletableFuture.supplyAsync(() -> {
              System.out.println("任务B, " + Thread.currentThread().getName());
              return 77;
          }), resultFirst -> {
              System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
              return resultFirst;
          }).exceptionally(e -> {
              // exception
              System.out.println("获取到异常, " + e.getMessage());
              return -1;
          });
          System.out.println("执行结果, " + task.join());
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      console

      任务A, ForkJoinPool.commonPool-worker-9
      任务B, ForkJoinPool.commonPool-worker-2
      获取到异常, java.lang.ArithmeticException: / by zero
      执行结果, -1
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • whenComplete:可以拿到返回结果同时也可以拿到出现的异常信息,但是whenComplete本身是Consumer不能返回结果。无法帮你捕获异常(程序执行的最后异常会抛出),但是可以拿到异常返回的结果

      public static void main(String[] args) throws IOException {
      	CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
              System.out.println("任务A, " + Thread.currentThread().getName());
              int i = 1/0;
              return 88;
          }).applyToEither(CompletableFuture.supplyAsync(() -> {
              System.out.println("任务B, " + Thread.currentThread().getName());
              return 77;
          }), resultFirst -> {
              System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
              return resultFirst;
          }).whenComplete((r, e) -> {
              System.out.println("获取到结果:" + r);
              System.out.println("获取到异常, " + e);
              System.out.println("这里会被执行");
          });
          System.out.println("执行结果, " + task.join()); // 此处会被whenComplete抛出的异常打断
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      console

      任务A, ForkJoinPool.commonPool-worker-9
      任务B, ForkJoinPool.commonPool-worker-2
      获取到结果:null
      获取到异常, java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
      这里会被执行
      Exception in thread "main" java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
      	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
      	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
      	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
      	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
      	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
      	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
      	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
      	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
      Caused by: java.lang.ArithmeticException: / by zero
      	at com.armin.thread.tool.CompletableFutureTest.lambda$testWhenComplete$0(CompletableFutureTest.java:28)
      	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
      	... 5 more
      
      Process finished with exit code 1
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • handle:可以拿到返回结果同时也可以拿到出现的异常信息,并且也可以指定返回托底数据。可以捕获异常的,异常不会抛出去

    whenCompletehandle这两个也是异常处理的套路,可以根据方法描述发现,他的功能方向比exceptionally要更加丰富

    public static void main(String[] args) throws IOException {
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A, " + Thread.currentThread().getName());
            int i = 1/0;
            return 88;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B, " + Thread.currentThread().getName());
            try {
                Thread.sleep(10); // 保证任务A先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 77;
        }), resultFirst -> {
            System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
            return resultFirst;
        }).handle((r, e) -> {
            System.out.println("获取到结果:" + r);
            System.out.println("获取到异常, " + e);
            System.out.println("这里会被执行");
            return -1;
        });
        System.out.println("执行结果, " + task.join());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    console

    任务A, ForkJoinPool.commonPool-worker-9
    任务B, ForkJoinPool.commonPool-worker-9
    获取到结果:null
    获取到异常, java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
    这里会被执行
    执行结果, -1
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.2.9 allOf,anyOf

    allOf的方式是让内部编写多个CompletableFuture的任务,多个任务都执行完后,才会继续执行你后续拼接的任务

    allOf返回的CompletableFutureVoid,没有返回结果

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        CompletableFuture<Void> task = CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务A");
                }),
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务B");
                }),
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务C");
                })
        ).thenRun(() -> {
            System.out.println("任务D");
        });
        System.out.println(task.join()); //阻塞主线程等待task任务执行完毕,便于查看执行过程
        System.out.println("执行时长:" + (System.currentTimeMillis() - start) + "ms");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    console

    任务A
    任务B
    任务C
    任务D
    null
    执行时长:3037ms
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    anyOf是基于多个CompletableFuture的任务,只要有一个任务执行完毕就继续执行后续,最先执行完的任务做作为返回结果的入参

    public static void main(String[] args) throws IOException {
    	long start = System.currentTimeMillis();
        CompletableFuture<Void> task = CompletableFuture.anyOf(
                CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务A");
                    return "A";
                }),
                CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务B");
                    return "B";
                }),
                CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务C");
                    return "C";
                })
        ).thenAccept(r -> {
            System.out.println("任务D执行," + r + "先执行完毕的");
        });
        System.out.println(task.join()); //阻塞主线程等待task任务执行完毕,便于查看执行过程
        System.out.println("执行时长:" + (System.currentTimeMillis() - start) + "ms");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    console

    任务A
    任务D执行,A先执行完毕的
    null
    执行时长:1050ms
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.3 CompletableFuture源码分析

    CompletableFuture的源码内容特别多。不需要把所有源码都看了,更多的是要掌握整个CompletableFuture的源码执行流程,以及任务的执行时机。

    CompletableFuture中比较简单的方法作为分析的入口,从而掌握整体执行的流程

    2.3.1 当前任务执行方式

    将任务和CompletableFuture封装到一起,再执行封装好的具体对象的run方法即可

    // 提交任务到CompletableFuture
    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        // asyncPool:执行任务的线程池
        // runnable:具体任务。
        return asyncRunStage(asyncPool, runnable);
    }
    
    // 内部执行的方法
    static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
        // 对任务做非空校验
        if (f == null) throw new NullPointerException();
        // 直接构建了CompletableFuture的对象,作为最后的返回结果
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        // 将任务和CompletableFuture对象封装为了AsyncRun的对象
        // 将封装好的任务交给了线程池去执行
        e.execute(new AsyncRun(d, f));
        // 返回构建好的CompletableFuture
        return d;
    }
    
    // 封装任务的AsyncRun类信息
    static final class AsyncRun extends ForkJoinTask<Void> implements Runnable, AsynchronousCompletionTask {
        // 声明存储CompletableFuture对象以及任务的成员变量
        CompletableFuture<Void> dep; 
        Runnable fn;
    
        // 将传入的属性赋值给成员变量
        AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
            this.dep = dep; 
            this.fn = fn;
        }
        // 当前对象作为任务提交给线程池之后,必然会执行当前方法
        public void run() {
            // 声明局部变量
            CompletableFuture<Void> d; Runnable f;
            // 将成员变量赋值给局部变量,并且做非空判断
            if ((d = dep) != null && (f = fn) != null) {
                // help GC,将成员变量置位null,只要当前任务结束后,成员变量也拿不到引用。
                dep = null; fn = null;
                // 先确认任务没有执行。
                if (d.result == null) {
                    try {
                        // 直接执行任务
                        f.run();
                        // 当前方法是针对Runnable任务的,不能将结果置位null
                        // 要给没有返回结果的Runnable做一个返回结果
                        d.completeNull();
                    } catch (Throwable ex) {
                        // 异常结束!
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    2.3.2 任务编排的存储&执行方式

    首先如果要在前继任务处理后,执行后置任务的话

    有两种情况:

    • 前继任务如果没有执行完毕,后置任务需要先放在stack栈结构中存储
    • 前继任务已经执行完毕了,后置任务就应该直接执行,不需要在往stack中存储了

    如果单独采用thenRun在一个任务后面指定多个后继任务,CompletableFuture无法保证具体的执行顺序,而影响执行顺序的是前继任务的执行时间,以及后置任务编排的时机

    2.3.3 任务编排流程
    // 编排任务,前继任务搞定,后继任务再执行
    public CompletableFuture<Void> thenRun(Runnable action) {
        // 执行了内部的uniRunStage方法,
        // null:线程池,现在没给。
        // action:具体要执行的任务
        return uniRunStage(null, action);
    }
    
    // 内部编排任务方法
    private CompletableFuture<Void> uniRunStage(Executor e, Runnable f) {
        // 后继任务不能为null,健壮性判断
        if (f == null) throw new NullPointerException();
        // 创建CompletableFuture对象d,与后继任务f绑定
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        // 如果线程池不为null,代表异步执行,将任务压栈
        // 如果线程池是null,先基于uniRun尝试下,看任务能否执行
        if (e != null || !d.uniRun(this, f, null)) {
            // 如果传了线程池,这边需要走一下具体逻辑
            // e:线程池
            // d:后继任务的CompletableFuture
            // this:前继任务的CompletableFuture
            // f:后继任务
            UniRun<T> c = new UniRun<T>(e, d, this, f);
            // 将封装好的任务,push到stack栈结构
            // 只要前继任务没结束,这边就可以正常的将任务推到栈结构中
            // 放入栈中可能会失败
            push(c);
            // 无论压栈成功与否,都要尝试执行以下。
            c.tryFire(SYNC);
        }
        // 无论任务执行完毕与否,都要返回后继任务的CompletableFuture
        return d;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    2.3.4 查看后置任务执行时机

    任务在编排到前继任务时,因为前继任务已经结束了,这边后置任务会主动的执行

    // 后置任务无论压栈成功与否,都需要执行tryFire方法
    static final class UniRun<T> extends UniCompletion<T,Void> {
    
        Runnable fn;
        // executor:线程池
        // dep:后置任务的CompletableFuture
        // src:前继任务的CompletableFuture
        // fn:具体的任务
        UniRun(Executor executor, CompletableFuture<Void> dep,CompletableFuture<T> src, Runnable fn) {
            super(executor, dep, src); this.fn = fn;
        }
    
        final CompletableFuture<Void> tryFire(int mode) {
            // 声明局部变量
            CompletableFuture<Void> d; CompletableFuture<T> a;
            // 赋值局部变量
            // (d = dep) == null:赋值加健壮性校验
            if ((d = dep) == null ||
                // 调用uniRun。
                // a:前继任务的CompletableFuture
                // fn:后置任务
                // 第三个参数:传入的是this,是UniRun对象
                !d.uniRun(a = src, fn, mode > 0 ? null : this))
                // 进到这,说明前继任务没结束,等!
                return null;
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);
        }
    }
    
    // 是否要主动执行任务
    final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
        // 方法要么正常结束,要么异常结束
        Object r; Throwable x;
        // a == null:健壮性校验
        // (r = a.result) == null:判断前继任务结束了么?
        // f == null:健壮性校验
        if (a == null || (r = a.result) == null || f == null)
            // 到这代表任务没结束。
            return false;
        // 后置任务执行了没? == null,代表没执行
        if (result == null) {
            // 如果前继任务的结果是异常结束。如果前继异常结束,直接告辞,封装异常结果
            if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
                completeThrowable(x, r);
            else
                // 到这,前继任务正常结束,后置任务正常执行
                try {
                    // 如果基于tryFire(SYNC)进来,这里的C不为null,执行c.claim
                    // 如果是因为没有传递executor,c就是null,不会执行c.claim
                    if (c != null && !c.claim())
                        // 如果返回false,任务异步执行了,直接return false
                        return false;
                    // 如果claim没有基于线程池运行任务,那这里就是同步执行
                    // 直接f.run了。
                    f.run();
                    // 封装Null结果
                    completeNull();
                } catch (Throwable ex) {
                    // 封装异常结果
                    completeThrowable(ex);
                }
        }
        return true;
    }
    
    // 异步的线程池处理任务
    final boolean claim() {
        Executor e = executor;
        if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
            // 只要有线程池对象,不为null
            if (e == null)
                return true;
            executor = null; // disable
            // 基于线程池的execute去执行任务
            e.execute(this);
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    前继任务执行完毕后,基于嵌套的方式执行后置

    // A:嵌套了B+C,  B:嵌套了D+E
    // 前继任务搞定,遍历stack执行后置任务
    // A任务处理完,解决嵌套的B和C
    final void postComplete() {
        // f:前继任务的CompletableFuture
        // h:存储后置任务的栈结构
        CompletableFuture<?> f = this; Completion h;
        // (h = f.stack) != null:赋值加健壮性判断,要确保栈中有数据
        while ((h = f.stack) != null ||
                // 循环一次后,对后续节点的赋值以及健壮性判断,要确保栈中有数据
               (f != this && (h = (f = this).stack) != null)) {
            // t:当前栈中任务的后续任务
            CompletableFuture<?> d; Completion t;
            // 拿到之前的栈顶h后,将栈顶换数据
            if (f.casStack(h, t = h.next)) {
                if (t != null) {
                    if (f != this) {
                        pushStack(h);
                        continue;
                    }
                    h.next = null;    // detach
                }
                // 执行tryFire方法,
                f = (d = h.tryFire(NESTED)) == null ? this : d;
            }
        }
    }
    
    // 回来了  NESTED == -1
    final CompletableFuture<Void> tryFire(int mode) {
        CompletableFuture<Void> d; CompletableFuture<T> a;
        if ((d = dep) == null ||
            !d.uniRun(a = src, fn, mode > 0 ? null : this))
            return null;
        dep = null; src = null; fn = null;
        // 内部会执行postComplete,运行B内部嵌套的D和E
        return d.postFire(a, mode);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2.4 CompletableFuture执行流程图

    image.png

    补充

    此图claim还有一种情况是线程池为null会让当前线程去同步调用(直接调用run

  • 相关阅读:
    最小生成树模板prim和kruskal
    测试面试 | 头铁硬肝自动化测试,跳槽薪资翻 3 倍(附面试题)
    什么叫做信息安全?包含哪些内容?与网络安全有什么区别?
    Hive效率优化记录
    头文件<cstddef> <cstdlib> <cstring> 常用的定义以及函数
    [Spring Boot]08 IDEA接入MyBatisCodeHelper代码自动生成器
    从Jenkins中获取Maven项目版本号
    Centos7 crontab 环境变量不生效
    3-3数据链路层-介质访问控制
    flask 请求对象
  • 原文地址:https://blog.csdn.net/min_chaoxi/article/details/133149497