11、一文详解CompletableFuture:来源、定义、方法、与场景使用分析

一、Future介绍

java创建线程的方式,一般常用的是Thread,Runnable。如果需要当前处理的任务有返回结果的话,需要使用Calabble。

Calabble运行需要配合Future。

Future是一个接口,一般会使用FutureTask实现类去接收Callable任务的返回结果。

FutureTask存在一些问题,同步非阻塞执行的任务,不会主动通知返回结果是什么。

二、FutureTask使用

Callable是你要执行的任务。

FutureTask是存放任务返回结果的位置。

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(()->{
            System.out.println("hello world");
            Thread.sleep(1000);
            return 123+11;
        });
       Thread t= new Thread(futureTask);
       t.start();

        System.out.println("main thread 线程启动了t线程处理任务");
        Integer i = futureTask.get();
        System.out.println("main thread 拿到结果"+i);
    }

三、FutureTask源码分析

分析FutureTask,首先需要查看他的核心属性。

java 复制代码
 /*    
	 * 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;//Callable的结果正在封装给当前的FutureTask
    private static final int NORMAL       = 2;//任务正常结束
    private static final int EXCEPTIONAL  = 3;//执行任务时,发生了异常
    private static final int CANCELLED    = 4;//任务被取消了
    private static final int INTERRUPTING = 5;//线程的中断状态被设置未了true(线程还在运行)
    private static final int INTERRUPTED  = 6;//线程被中断了。
	//当前要执行的任务
    private Callable<V> callable;
	//存放任务返回结果的属性,也就是futureTask.get需要获取的结果
    private Object outcome; // non-volatile, protected by state reads/writes
	//执行任务的线程
    private volatile Thread runner;
	//单向链表,存放通过get方法挂起等待的线程
    private volatile WaitNode waiters;

t.start之后,如何执行Callable的call方法,是通过run方法执行的call方法

java 复制代码
//futureTask重新了run方法,执行流程,最终会执行callable的call方法	
public void run() {
     //保障任务的状态是new才可以运行
	 //基于cas的方式,将当前的线程设置为runner
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
		//准备执行任务	
        try {
		//要执行的任务c
            Callable<V> c = callable;
			//任务不为null且任务的状态还处于new
            if (c != null && state == NEW) {
			//放返回结果
                V result;
				//任务执行是否为正常结束
                boolean ran;
                try {
				//运行call方法,拿到返回结果封装到result中
                    result = c.call();
					//正常返回,ran设置为true
                    ran = true;
                } catch (Throwable ex) {
				//结果为null
                    result = null;
					//异常返回,ran设置为false
                    ran = false;
					//设置异常信息
                    setException(ex);
                }
                if (ran)
				//正常执行结束,设置返回结果
                    set(result);
            }
        } finally {
		//将执行任务的runner设置为空
            runner = null;
			//拿到状态
            int s = state;
			//中断要做一些后续处理
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
	

//设置返回结果
protected void set(V v) {
     //首先要将任务状态从new设置为completing
        if (STATE.compareAndSet(this, NEW, COMPLETING)) {
		//正常的返回结果,将返回结果设置给outcome
            outcome = v;
			//将状态修改为normal,代表正常结束
            STATE.setRelease(this, NORMAL); // final state
			//后面再说
            finishCompletion();
        }
    }

get方法获取返回结果

java 复制代码
public V get() throws InterruptedException, ExecutionException {
     //拿状态
        int s = state;
		//满足这个状态代表现在可能还没有返回结果
        if (s <= COMPLETING)
		//尝试挂起线程,等待拿结果
            s = awaitDone(false, 0L);
        return report(s);
    }
	

//线程要等待任务执行结果拿返回结果,等待任务执行的状态变为大于COMPLETING状态
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    //计算deadline,如果是get(),就是0,如果是get(time,unit)那就追加当前系统时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
	//构建waitNode
    WaitNode q = null;
    boolean queued = false;
    //死循环
    for (;;) {
        // 1. 检查中断:get的线程是否中断
        if (Thread.interrupted()) {
		//将当前节点从waiter中移除
            removeWaiter(q);
			//并且抛出中断异常
            throw new InterruptedException();
        }
        
        // 2. 检查任务状态:拿到现在任务的状态
        int s = state;
		//判断任务是否已经执行结束了
        if (s > COMPLETING) {
		//如果设置为waitNode,直接移除waitNode的线程
            if (q != null)
                q.thread = null;
				//返回当前任务的状态
            return s;
        }
		//如果任务的状态处于COMPLETING,线程让渡
        else if (s == COMPLETING) {
		//COMPLETING持续时间短,只需要做一手线程的让渡即可。
            Thread.yield();
        }
        // 3. 创建等待节点:现在线程的状态是new,call方法可能还没执行完,准备挂起线程
        else if (q == null) {
		//封装waitNode存放当前线程 
            q = new WaitNode();
        }
        // 4. 加入等待队列(第一次循环可能未进来,第二次进来)
        else if (!queued) {
		//如果waitNode还没有排在waiters中,现在就排进来(头插法的效果)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        }
        // 5. 阻塞等待
        else if (timed) {
		//get(time,unit)挂起线程的方式
		//计算挂起时间
            nanos = deadline - System.nanoTime();
			//挂起的时间是否<=0,移除
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
			//指定挂起时间,线程挂起 
            LockSupport.parkNanos(this, nanos);
        }
		//get()挂起线程的方式
        else {
            LockSupport.park(this);
        }
    }
}

线程挂起后,如果任务执行完成,由finishCompletion唤醒线程

java 复制代码
//任务状态已经变为normal,做一些后续处理
private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
		//拿到第一个节点后,直接用cas的方式,将其设置为null
            if (WAITERS.weakCompareAndSet(this, q, null)) {
                for (;;) {
				//基于q拿到线程信息
                    Thread t = q.thread;
					//线程不为null
                    if (t != null) {
					//将waitingode的thread设置为null
                        q.thread = null;
						//唤醒这个线程
                        LockSupport.unpark(t);
                    }
					//完后遍历,接着唤醒
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
					//指向next的waitNode
                    q = next;
                }
                break;
            }
        }
		//扩展方法,可以自己实现
        done();
		//任务处理结束,完成
        callable = null;        // to reduce footprint
    }

拿到返回结果的处理

java 复制代码
	//任务结束
	private V report(int s) throws ExecutionException {
	//拿到结果
        Object x = outcome;
		//判断是正常返回结果
        if (s == NORMAL)
            return (V)x;
			//任务状态是大于取消,抛出异常
        if (s >= CANCELLED)
            throw new CancellationException();
		//丢异常
        throw new ExecutionException((Throwable)x);
    }
	//正常返回 report 
	//异常返回 report 
	//取消任务  report 
	//中断任务 awaitDone

四、CompletableFuture介绍

FutureTask存在问题

  • 问题1-主线程阻塞-get:FutureTask获取线程执行的结果前,主线程需要通过get方法,一直阻塞着等待,直到线程执行完call方法后,才能拿到对应数据。

  • 问题2-同步-isDone:如果不通过get去挂起线程,通过while循环,不停的去判断任务的执行状态是否结束,结束后,再拿结果。如果任务长时间没执行完毕,CPU会一直调度查看任务状态的方法,会浪费CPU资源。

    FutureTask是一个同步非阻塞处理任务的方式。

    需要一个异步非阻塞处理任务的方式。CompletableFuture再一定程度上提供了各种异步非阻塞的处理方案,并且提供响应式编程,代码编写上,效果更佳(更漂亮)。

    CompletableFuture是JDK1.8,也实现了FutureTask,直接使用CompletableFuture即可。

    提供了非常丰富的函数去执行各种异步操作。

初识CompletableFuture应用:

java 复制代码
//正常返回 report 
	//异常返回 report 
	//取消任务  report 
	//中断任务 awaitDone
	
	
	 /**
     * 打印:两个任务同时执行,但是最终需要合并
     * ForkJoinPool.commonPool-worker-5:执行二个任务
     * ForkJoinPool.commonPool-worker-19:执行一个任务
     * 执行第一个任务结束执行第二个任务结束
     * @return
     */

    private static String twoTaskAddJoin() {
       return CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+":执行一个任务");
            return "执行第一个任务结束";
            //后一任务异步执行,
        }).thenCombineAsync(CompletableFuture.supplyAsync(()->{
           System.out.println(Thread.currentThread().getName()+":执行二个任务");
           return "执行第二个任务结束";
       }),(result1,result2)->{
            return result1+result2;
        }).join();
    }

    /**
     * 打印:第二个任务可以同步执行,但是某一步需要依赖前一任务
     * ForkJoinPool.commonPool-worker-19:执行一个任务
     * ForkJoinPool.commonPool-worker-19:执行二个任务
     * 执行第一个任务结束
     * 执行第二个任务结束
     * @return
     */
    private static String twoAsyncTaskJoin() {
        //异步执行一个功能
      return  CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+":执行一个任务");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "执行第一个任务结束";
            //后一任务异步执行,
        }).thenApplyAsync(result->{
          System.out.println(Thread.currentThread().getName()+":执行二个任务");
          //第一个任务执行结束
            System.out.println(result);
            return "执行第二个任务结束";
        }).join();
    }

    /**
     * 打印:第二任务依赖前一任务
     *    main线程获取结果
     *    ForkJoinPool.commonPool-worker-19:执行一个任务
     *    执行第一个任务结束
     *    执行第二个任务结束
     */
    private static void twoTask() {
        //异步执行一个功能
        CompletableFuture<String> future= CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+":执行一个任务");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "执行第一个任务结束";
        }).thenApply(result->{
            System.out.println(result);
            return "执行第二个任务结束";
        });
        System.out.println("main线程获取结果");
        System.out.println(future.join());
    }

总结下:

FutureTask是同步非阻塞。

FutureTask会配合Callable执行有返回结果的任务。

如果需要拿到返回结果,需要执行get方法,获取最终结果。

因为无法实现异步非阻塞,CompletableFuture可以实现异步非阻塞的效果再带有返回结果的线程执行完毕后,提供一个回调。


4.1 CompletableFuture的常见方法

CompletableFuture最重要的就是解决了异步回调的问题

CompletableFuture就是执行了一个异步任务,异步任务可以有返回结果,也可以没有返回结果。

CompletableFuture提供了两个最基本运行的基本方法。

函数式编程中,三个最核心的接口:

java 复制代码
Supplier-生产者,没有入参,但是有返回结果
Consumer-消费者,有入参,但是没有返回结果
Function-函数,有入参,并且有返回结果

supplyAsync(Supplier++Supplier)
异步执行任务,有返回结果
runAsync(Runnable runnable)
异步执行任务,没有返回结果
在不指定线程池的前提下,这两个异步任务都是交给ForkJoinPool去执行的。
而ForkJoinPool内部是守护线程,守护线程在主线程执行完后就不干活了。++


但是只只用这两个方法,无法实现异步回调的。

需要在当前任务执行完毕后,拿着返回结果或者不拿返回结果,继续去执行后续任务操作的话,需要基于其他方法去实现。

这里的方法有个特点,都是在前置任务执行完后,再去执行当前任务

java 复制代码
/**
*两个参数:第一个是上一个返回结果,第二个是下一个返回结果
*/
thenApply(    Function<? super T,? extends U> fn)  
/**
* demo如下
*/
CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("前置任务处理");
            return null;
        }).thenApply(result->{
            System.out.println("上一个任务返回结果:"+result);
            System.out.println("后续任务处理");
            return 123;
        });

        task.join();
等待前一个任务处理结束后,拿着前置任务的返回结果,再做处理,并且返回当前任务。

thenApplyAsync 方法解析

java 复制代码
thenApplyAsync(    Function<? super T,? extends U> fn)
实例如下:
CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            sout("前置任务处理");
            return null;
        },executorService).thenApplyAsync(result->{
            sout("上一个任务返回结果:"+result);
            sout("后续任务处理");
            return 123;
        },executorService);

        task.join();
 
跟上面的套路一致,但是再执行后续任务时,采用全新的线程执行。
所以后续看的任务都有一个特点,大部分方法一共有三种方法重载。
不带Async,带Async和带Async还可以传入线程池的套路

thenAccept方法解析

java 复制代码
等待前一个任务处理结束后,拿着前置任务的返回结果,再做处理,当前处理没有返回结果
thenAccept(Consumer<? super T> action)

thenRun方法解析

复制代码
//等待前一个任务处理结束后,再做处理,不接收前置任务结果,也不返回结果
thenRun(Runnable action,线程池)

其次还有可以执行相对复杂的处理,在前一个任务执行的同时,执行后续任务,等待前置任务和后置任务都搞定之后,再执行最终任务。

thenCombine

java 复制代码
thenCombine(completionStage,Function<preResult,nextResult,afterResult>)
可以让任务1和任务2一起执行,等待任务1和任务2全部搞定,获取前两个任务的结果执行最终处理,最终处理也可以返回结果

执行案例如下:

java 复制代码
CompletableFuture<Integer> task=CompletableFuture.supplyAsync(() -> {
            sout("前置任务处理");
            return 1;
        },executorService).thenCombine(CompletableFuture.supplyAsync(() -> {
            sout("后续任务处理");
            return 2;
        }),(result1,result2)->{
            sout("最终任务处理:"+result1+"  "+result2);
            return result1+result2;
        });
        System.out.println(task.get());

thenAcceptBoth方法分析

java 复制代码
//让前置任务和后续任务同时执行,都执行完毕后,拿到两个任务的结果,再做后续处理,但是没有返回结果
thenAcceptBoth(completionStage,Consumer<prevResult,nextResult>)

runAfterBoth方法分析

java 复制代码
//让前置任务和后续任务同时执行,都执行完毕后,再做后续处理
runAfterBoth(completionStage,Runnable)

runAfterBothAsync(completionStage,Runnable,线程池)

后面还提供了可以让两个任务一起执行,但是有一个任务结束,有返回结果后,就做最终处理。

applyToEither分析

java 复制代码
//前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理,再返回结果
applyToEither(completionStage,function<firstResult,AfterResult>)
//前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理
applyToEither(completionStage,function<firstResult>)
//前面两个任务同时执行,有一个任务执行完,直接做最终处理 
applyToEither(completionStage,Runnable)

后面还提供了等到前置任务处理完,再做后续处理,后续处理返回的结果为CompletionStage

java 复制代码
//连接两个任务,前置处理完,执行后续,后续可以拿到前置处理任务的结果,并且做处理,最终返回的是CompletionStage
thenCompose(function<prevResult,completionStage>)

//案例如下:不过一般性用thenApply就足够了
 var task=CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenCompose(result->{
            return CompletableFuture.supplyAsync(() -> {
                sout("前置任务处理");
                return null;
            });
        });
        task.join();

最后还有处理异常的各种方式

exceptionally分析

java 复制代码
exceptionally(function<Throwable,currResult>)
//案例
var task=CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenCompose(result->{
            return CompletableFuture.supplyAsync(() -> {
                sout("前置任务处理");
                return null;
            });
        }).exceptionally(ex->{
            sout("异常处理:"+ex.getMessage());
            return 0;
        });
        task.join();

whenComplete分析

java 复制代码
//可以拿到上一个任务的返回结果和异常,但是当前处理没有返回结果,无法影响最终让任务的结果内容,带有Async操作
whenComplete(consumer<preResult,throwabl>))
//可以拿到上一个任务的返回结果和异常,当前处理有返回结果信息
handle(consumer<preResult,throwabl,currResult>))
4.1 CompletableFuture的应用

例子1.小明要回家吃饭,妻子做饭,小明看电视,等到妻子做完,小明吃饭。

java 复制代码
sout("小明回家吃饭");
        var task=CompletableFuture.supplyAsync(()->{
            sout("老婆做饭");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "水煮鱼";
        });
        sout("小明看电视");
        System.out.println("小明吃饭:"+task.join());

例子2.小明回家吃饭,妻子炒菜,儿子焖饭,小明看电视,等到菜和饭做好,女儿端饭,小明吃饭。

java 复制代码
 sout("小明回家吃饭");
        var task=CompletableFuture.supplyAsync(()->{
            sout("老婆做菜");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "水煮鱼";
        },executorService).thenCombine(CompletableFuture.supplyAsync(()->{
            sout("孩子做饭");
            try {
                Thread.sleep(2500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "大米饭";
        },executorService),(food,rice)->{
            sout("女儿端上:"+food+"和"+rice);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "饭菜好了";
        });
        sout("小明看电视");
        System.out.println("小明吃饭:"+task.join());

五、使用场景分析

5.1 三个以上表并行查询如何处理

解决方案一、启用线程处理方式

通过Thread+FutureTask方式进行获取处理

优势:可以通过多线程获取到对应数据

劣势:代码复杂度高,且线程复用性差

java 复制代码
FutureTask<String> task1 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "1";
            }
        });
        Thread t1=new Thread(task1);
        t1.start();
        FutureTask<String> task2 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "2";
            }
        });
        Thread t2=new Thread(task2);
        t2.start();

        System.out.println("结果:"+task1.get()+"task2:"+task2.get());

解决方案二、线程池并行处理

通过线程池的submit方式,进行并行查询处理。

原理:此类方法是同步非阻塞方式运行,即方法需要显性调用才行,不可进行隐形调用

优势:支持数据的并行处理 ,相对于线程方式,提高了线程复用效率

劣势:仅能支持线程池并行处理,无法支持任务间前后依赖依赖,使用场景有限

java 复制代码
ExecutorService executorService = Executors.newFixedThreadPool(10);
        //多线程同时查询三个不同表
        //submit 查询数据返回实例
        Future<Object> submit1 = executorService.submit(new Callable<Object>() {

                                                           @Override
                                                           public Object call() throws Exception {
                                                               Thread.sleep(2000);
                                                               return "1";
                                                           }
                                                       }
        );
        Future<Object> submit2 = executorService.submit(new Callable<Object>() {

                                                           @Override
                                                           public Object call() throws Exception {
                                                               Thread.sleep(2000);
                                                               return "2";
                                                           }
                                                       }
        );
        Future<Object> submit3 = executorService.submit(new Callable<Object>() {

                                                            @Override
                                                            public Object call() throws Exception {
                                                                Thread.sleep(2000);
                                                                return "3";
                                                            }
                                                        }
        );

        System.out.println("结果sub1:"+submit1.get()+"sub2:"+submit2.get()+"sub3:"+submit3.get());

解决方案三、List

通过定义List进行并行处理

原理:此类方法是利用List支持并行流进行处理,默认将线程提交给了forkjoin线程池

ForkJoinPool.commonPool()

优势:支持数据的并行处理 ,代码简单方便

劣势:线程池默认,无法进行自定义配置;且数值需要通过对象进行传递才行

java 复制代码
private static void listRuabbleTest() {
        List<Runnable> list=new ArrayList<>();
        StringBuilder t1=new StringBuilder();
        StringBuilder t2=new StringBuilder();
        list.add(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                t1.append("1");
            }
        });
        list.add(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                t2.append("1");
            }
        });
        list.parallelStream().forEach(Runnable::run);
        System.out.println("结果:"+t1.toString()+"t2:"+t2.toString());
    }

解决方案四、CompletableFuture方案

通过CompeletableFuture方案,通过并行获取拿到结果

原理:采用异步非阻塞方式,

优势:并行获取效率较高,且支持自定义线程池方式进行处理

java 复制代码
private static void completableFutureTest() {
        //默认forkjoin线程池
        List<String> list=new CopyOnWriteArrayList<>();
        CompletableFuture.runAsync(()->{
            list.add("1");
        }).runAsync(()->{
            list.add("2");
        }).runAsync(()->{
            list.add("3");
        }).join();
        System.out.println("结果:"+list.stream().collect(Collectors.joining()));
    }
    //自定义线程池方式
    ExecutorService executorService = Executors.newFixedThreadPool(10);
        CompletableFuture.allOf(CompletableFuture.runAsync(()->{
            list.add("1");
        },executorService),CompletableFuture.runAsync(()->{
            list.add("2");
        },executorService),CompletableFuture.runAsync(()->{
            list.add("3");
        },executorService)).join();
        System.out.println("结果:"+list.stream().collect(Collectors.joining()));

5.2 任务串并联多种方式

如图所示,处理思路先从主干再到分支。

步骤1:任务1主干-任务2主干

步骤2:任务2->步骤3分支并行

步骤3:任务2->任务4分支串行

步骤4:任务4->任务5 6 7 8,分支并行

java 复制代码
 CompletableFuture<String> task1 = CompletableFuture.supplyAsync(()->{
            try {
                System.out.println("开始执行任务1");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("任务1执行完成");
            return "任务一完成";
        }).thenApply(s->{
                System.out.println("开始执行任务2");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            System.out.println("任务2执行完成");
            return s+"--任务二完成";
        }).thenApplyAsync(s->{
            System.out.println("任务3执行完成");
            return s+"--任务三完成";
        }).thenApplyAsync(s->{
            System.out.println("任务四完成");
         var tt1=CompletableFuture.allOf(CompletableFuture.supplyAsync(()->{
                        System.out.println("开始执行任务5");
            return "任务五完成";
            }).thenApplyAsync(s1->{
                System.out.println("任务6执行完成");
                return s1+"--任务6完成";
                    })
                    .thenApplyAsync(s1->{
                        System.out.println("任务7执行完成");
                        return s1+"--任务7完成";
                    })
                    .thenApplyAsync(s1->{
                        System.out.println("任务8执行完成");
                        return s1+"--任务8完成";
                    })
            ).join();
            return s+"--"+tt1;
        });
        System.out.println(task1.join());

5.3 需要等待多个任务执行完如何处理

使用场景:此处是一个算法exe执行监控,需要满足算法exe执行完成,且文件生成完成后才将文件结果合并并发送消息信息。

java 复制代码
private static void completableFutureTest1() {
        //等待处理完成
        CompletableFuture.supplyAsync(()->{
            System.out.println("执行算法任务");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("算法任务完成");
            return "任务完成";
        }).thenCombine(CompletableFuture.supplyAsync(()->{
            System.out.println("检查文件信息");
            return "";
        }), (s, s2) -> {
            System.out.println("算法执行正常,结束执行");
            return "完成";
        }).exceptionally(
                throwable -> {
                    System.out.println("算法执行异常");
                    return "异常处理完成";
                }
        ).join();
        System.out.println("算法执行结束,发送结束标识位信息");
    }
相关推荐
Kin__Zhang1 小时前
随手记录 UE4/CARLA 仿真器 segmentation fault
android·java·ue4
CoderYanger1 小时前
A.每日一题——1523. 在区间范围内统计奇数数目
java·数据结构·算法·leetcode·职场和发展
期待のcode1 小时前
MyBatis-Plus通用Service
java·后端·mybatis·springboot
程序员-周李斌1 小时前
ArrayBlockingQueue 源码解析
java·开发语言·后端·哈希算法·散列表
2501_941982052 小时前
系统集成与生态建设:将企业微信 RPA 自动化能力融入现有平台
大数据·网络
编程修仙2 小时前
第一篇 认识SpringBoot
java·spring boot
骇客野人2 小时前
.gitignore文件常用设置
java
bill4472 小时前
BPMN2.0,flowable工作流,【用户任务】使用【任务监听器】动态设置下一步处理人
java·工作流引擎·flowable·bpmn
Cricyta Sevina2 小时前
Java 语言多线程核心概念全解析
java·开发语言