CompletableFuture小记

文章目录

    • 概述
    • Thread 和 Runnable
      • Thread
      • Runnable
      • Thread 和 Runnable 的关系
      • 小问题
    • Callable、Future 和 FutureTask
      • Callable
      • FutureTask
      • Future接口
      • Callable 和 FutureTask 的关系
      • Thread 、Runnable、FutureTask 和 Callable 的关系
    • CompletableFuture
      • 常用方法概述
      • get() 和 join() 方法区别
      • supplyAsync
      • runAsync
      • thenApply
      • thenApplyAsync(自定义线程)
      • thenAccept
      • thenAcceptAsync(自定义线程)
      • thenRun
      • thenRunAsync(自定义线程)
      • thenCombine
      • thenAcceptBoth
      • runAfterBoth
      • allOf
      • anyOf

概述

平时多线程开发大多是使用Runnable,Callable,Thread,FutureTask,ThreadPoolExecutor等方式开启多线程,这些方式开启多线程的优势是成本不高。但是通过上述几种方式开启多线程很多时候没有办法很方便的去完成异步编程的操作,原因如下:

  • Thread + Runnable:可以执行异步任务,没有返回结果,无法抛出异常
  • Callable + Future + FutureTask:可以执行异步任务,可以获取返回结果,可以抛出异常,所以当你需要返回多线程的数据,就需要借助
    Callable 和 Future
    • 获取返回结果,如果基于get方法获取,线程需要挂起在WaitNode里
    • 获取返回结果,也可以基于isDone判断任务的状态,但是这里需要不断轮询

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

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

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

CompletionStage接口定义了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池。

Thread 和 Runnable

Thread

部分源代码如下:

public class Thread implements Runnable { private Runnable target;@Overridepublic void run() {if (target != null) {target.run();}}//.........
}

简单使用方式:继承该类并重写其run()方法

public class MyThread01 extends Thread {public MyThread01(String name) {super(name);}@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(name + "已经运行");}public static void main(String[] args) {new MyThread01("线程一").start();}
}

Runnable

部分源代码如下:该接口只有一个方法,所以它是一个函数式接口

@FunctionalInterface
public interface Runnable {public abstract void run();
}

简单使用方式:实现该接口并重写其run()方法

public class MyThread02 implements Runnable {@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(name + "已经运行");}public static void main(String[] args) {new Thread(new MyThread02(),"线程二").start();}
}

Thread 和 Runnable 的关系

Thread类本身也实现了Runable接口,并重写了Runbale接口提供的run方法。

Thread方式是继承Thread类,Runbale方式是实现Runable接口。

虽然上面说的是两种方式启动多线程,但是本质都是使用了JDK提供的Thread类来启动多线程的。

注意:Runbale方式的实现是因为Thread类提供了入参为Runable类型的构造方法。

小问题

为什么线程启动调用的是Thread.start()方法,而不是Thread.run()方法?

Callable、Future 和 FutureTask

为什么已经有了 Thread 和 Runnable 这两种创建线程的方式,还要出现另外的那?

其实对于 Thread 和 Runable,其 run() 都是无返回值的,并且无法抛出异常,所以当你需要返回多线程的数据,就需要借助 Callable 和 Future。

Callable

部分源码如下:该接口只有一个方法,所以它也是一个函数式接口

@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}
Callable接口在:java.util.concurrent包下功能:类似于Runnable接口,可以创建一个可以被其他线程执行的实例差别:Callable接口- 可以有返回值- 可以抛出异常- 方法不同,一个是run(),一个是call()

注意:java启动线程必须要和Thread类扯上关系。

FutureTask

部分源码如下:

public class FutureTask<V> implements RunnableFuture<V> {public void run() {// ......}// ......
}

java启动线程必须要和Thread类扯上关系,通过查Thread类发现,此类的构造方法中只能接收Runnable的实现类,所以要想使用Callable创建线程,必须要和Thread或者Runnable接口扯上关系。

经过查看JDK文档发现:

  • Runnable有个实现类FutureTask,在java.util.concurrent包下面
  • FutureTask有个构造可以接收Callable接口的实现类
  • 所以,FutureTask 也是个 Runnable,所以当执行 Thread.run() 时,其实是执行的 FutureTask.run()

于是乎我们可以这样

  • 1.创建一个类实现Callable接口(通过泛型指定返值的类型)
  • 2.把实现类的实例放到FutureTask的构造中,FutureTask是Runnable接口的实现类
  • 3.把FutureTask的实例放到Thread中用来创建线程

简单使用方式:

public class CallableTest01 {public static void main(String[] args) {MyThread03 myThread03 = new MyThread03();// 2.把实现类的实例放到FutureTask的构造中,FutureTask是Runnable接口的实现类FutureTask futureTask=new FutureTask(myThread03);// 3.把FutureTask的实例放到Thread中用来创建线程new Thread(futureTask,"Thread_A").start();// 使用匿名内部类的方式new Thread(new FutureTask<String>(new MyThread03()),"Thread_B").start();}
}// 1.创建一个类实现Callable接口(通过泛型指定返值的类型)
//注意:泛型的参数等于方法的返回值
class MyThread03 implements Callable<String>{@Overridepublic String call() throws  InterruptedException{System.out.println("Callable.call.....");return "aismall";}
}

Future接口

部分源码如下:

public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

前面说了借助 Callable 和 Future可以获取多线程的返回值,这个返回值在FutureTask里面,使用get方法获取返回值,FutureTask.get()方法是由Future接口提供的。

简单使用方式:

public class CallableTest01 {public static void main(String[] args) {MyThread03 myThread03 = new MyThread03();// 2.把实现类的实例放到FutureTask的构造中,FutureTask是Runnable接口的实现类FutureTask futureTask=new FutureTask(myThread03);// 3.把FutureTask的实例放到Thread中用来创建线程new Thread(futureTask,"Thread_A").start();// 使用匿名内部类的方式new Thread(new FutureTask<String>(new MyThread03()),"Thread_B").start();try {String returnValue=(String) futureTask.get(); //此方法会产生阻塞,一般往后放System.out.println(returnValue);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}// 1.创建一个类实现Callable接口(通过泛型指定返值的类型)
//注意:泛型的参数等于方法的返回值
class MyThread03 implements Callable<String>{@Overridepublic String call() throws  InterruptedException{System.out.println("Callable.call.....");return "aismall";}
}

对于 FutureTask,Callable 就是他的任务,而 FutureTask 内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。

FutureTask 继承了 Future,实现对任务的取消、数据获取、任务状态判断等功能。

比如我们经常会调用 get() 方法获取数据,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。

Callable 和 FutureTask 的关系

FutureTask 初始化时,Callable 必须作为 FutureTask 的初始化入参:

public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW;       // ensure visibility of callable}

当执行 FutureTask.run() 时,其实执行的是 Callable.call():

public void run() {if (state != NEW ||!RUNNER.compareAndSet(this, null, Thread.currentThread()))return;try {// CallableCallable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {// Callable.call()result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {runner = null;int s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}
}

Thread 、Runnable、FutureTask 和 Callable 的关系

现在我们应该可以很清楚知道 :Thread 、Runnable、FutureTask 和 Callable 的关系

  • Thread.run() 执行的是 Runnable.run():因为Thread类实现了Runable接口的run()方法,所以执行顺序是:Thread.run()——》Runable.run()。
  • FutureTask.run() 执行的是 Callable.run():因为内部做了处理,最终执行的是Callable接口实现类的run方法,所以执行顺序是:FutureTask.run()—》Callable.run()。
  • FutureTask 继承了 Runnable接口并实现了该接口的run方法,所以执行顺序是:Thread.run()——》Runnable.run()—》FutureTask.run()—》Callable.run()。

最终线程的启动都是使用Thread.start()方法来完成的。

CompletableFuture

常用方法概述

  • supplyAsync() :开启异步(创建线程执行任务),有返回值

  • thenApply() :拼接异步任务,前置任务(必须有返回值)处理完之后,将返回结果交给后置任务,然后后置任务再执行,后置任务也有返回结果。

  • thenAccept():拼接异步任务,前置任务(必须有返回值)处理完之后,将返回结果交给后置任务,然后后置任务再执行,后置任务没有返回结果。

  • runAsync() :开启异步(创建线程执行任务),无返回值。

  • thenRun():拼接异步任务,前置任务(返回值可有可无)处理完之后,不接受前置任务执行结果,然后后置任务再执行,后置任务也没有返回结果。

  • get():阻塞线程,主要可以: ①获取线程中的异常然后处理异常、②设置等待时间

  • join():阻塞线程,推荐使用 join() 方法,因为它没有受到 interrupt 的干扰,不需要捕获异常,也不需要强制类型转换。他自己会抛出异常。

  • thenCombine():处理并行任务,如有任务A,任务B,任务C,任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务需要有返回结果,后置任务接收前置任务的结果,有返回值。

  • thenAcceptBoth():处理并行任务,如有任务A,任务B,任务C,任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务需要有返回结果,后置任务接收前置任务的结果,没有返回值。

  • runAfterBoth():处理并行任务,如有任务A,任务B,任务C。任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务不需要有返回结果,后置任务不会接收前置任务的结果,没有返回值。

  • applyToEither():如有任务A,任务B,任务C。任务A和任务B并行执行,只要任务A或者任务B执行完毕,开始执行任务C,可以接收结果并且返回结果。

  • acceptEither():如有任务A,任务B,任务C。任务A和任务B并行执行,只要任务A或者任务B执行完毕,开始执行任务C,可以接收结果没有返回结果。

  • runAfterEither():如有任务A,任务B,任务C。任务A和任务B并行执行,只要任务A或者任务B执行完毕,开始执行任务C,不接收结果也没返回结果。

  • exceptionally():拼接异步任务,但是只有前面业务执行时出现异常了,才会执行当前方法来处理,:返回默认值或者一个替代的 CompletableFuture 对象,从而避免系统的崩溃或异常处理的问题。

  • handle():类似exceptionally()。

  • allOf():让内部编写多个CompletableFuture的任务,多个任务都执行完后,才会继续执行你后续拼接的任务,返回的CompletableFuture是Void,没有返回结果。

  • anyOf():让内部编写多个CompletableFuture的任务,只要有一个任务执行完毕就继续执行后续,最先执行完的任务做作为返回结果的入参。

get() 和 join() 方法区别

get() 和 join() 方法:都可以阻塞线程,等所有任务都执行完了再执行后续代码。

CompletableFuture 中的 get() 和 join() 方法都用于获取异步任务的执行结果,但是在使用时需要注意以下几点区别:

  • 抛出异常的方式不同:如果异步任务执行过程中出现异常, get() 方法会抛出 ExecutionException 异常,而 join() 方法会抛出 CompletionException 异常,这两个异常都是继承自 RuntimeException 的。

  • 方法调用限制不同: join() 方法是不可以被中断的,一旦调用就必须等待任务执行完成才能返回结果;而 get() 方法可以在调用时设置等待的超时时间,如果超时还没有获取到结果,就会抛出 TimeoutException 异常。

  • 返回结果类型不同: get() 方法返回的是异步任务的执行结果,该结果是泛型类型 T 的,需要强制转换才能获取真正的结果;而 join() 方法返回的是异步任务的执行结果,该结果是泛型类型 T,不需要强制转换。

  • 推荐使用方式不同:推荐在 CompletableFuture 中使用 join() 方法,因为它没有受到 interrupt 的干扰,不需要捕获异常,也不需要强制类型转换。

综上所述, get() 方法和 join() 方法都是获取异步任务的执行结果,但是在使用时需要根据具体场景选择使用哪个方法。如果需要获取执行结果并且不希望被中断,推荐使用 join() 方法;如果需要控制等待时间或者需要捕获异常,则可以使用 get() 方法。

supplyAsync

示例代码:

public static void main(String[] args) {// 开启异步(创建线程执行任务),有返回值CompletableFuture<String> firstTask = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程名字: " + Thread.currentThread().getName());return "返回结果";});System.out.println("执行吗 222222");// 阻塞等待等待线程执行完成返回结果 不用捕获异常String result1 = firstTask.join();System.out.println("result1: " + result1);try {// 阻塞等待线程执行完成返回结果 需要捕获异常String result2 = firstTask.get();System.out.println("result2: " + result2);} catch (Exception e) {e.printStackTrace();}}

运行结果:

执行吗 222222
执行了吗 111111
线程名字: ForkJoinPool.commonPool-worker-1
result1: 返回结果
result2: 返回结果

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

runAsync

示例代码:

public static void main(String[] args) {CompletableFuture<Void> firstTask = CompletableFuture.runAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程名字: " + Thread.currentThread().getName());});System.out.println("执行吗 222222");}

运行结果:

执行吗 222222
执行了吗 111111
线程名字: ForkJoinPool.commonPool-worker-1

小结:和普通的开启异步线程一样,无返回值,异步线程和main线程交替执行,谁先抢占到CPU谁先执行。

thenApply

示例代码:

public static void main(String[] args) {// 开启异步(创建线程执行任务),有返回值CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";});String resultA = taskA.join();System.out.println("resultA: " + resultA);// 拼接异步任务CompletableFuture<String> taskB = taskA.thenApply(result -> {System.out.println("任务B获取到任务A结果: " + result);System.out.println("线程B名字: " + Thread.currentThread().getName());result = result + "--222222";return result;});String resultB = taskB.join();System.out.println("resultB: " + resultB);}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
resultA: 返回结果 1111111
任务B获取到任务A结果: 返回结果 1111111
线程B名字: main
resultB: 返回结果 1111111--222222

小结:有任务A还有任务B,任务B需要在任务A执行完毕后再执行,而且任务B需要任务A的返回结果,任务B自身也有返回结果,thenApply可以拼接异步任务,前置任务处理完之后,将返回结果交给后置任务,然后后置任务再执行

thenApplyAsync(自定义线程)

示例代码:

public static void main(String[] args) {// 自定义线程池ExecutorService executor = Executors.newFixedThreadPool(10);// 开启异步(创建线程执行任务),有返回值CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).thenApplyAsync(result -> { // 拼接异步任务,指定自定义线程池System.out.println("任务B获取到任务A结果: " + result);System.out.println("线程B名字: " + Thread.currentThread().getName());result = result + "--222222";return result;}, executor);String resultB = taskB.join();System.out.println("resultB: " + resultB);}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
任务B获取到任务A结果: 返回结果 1111111
线程B名字: pool-1-thread-1
resultB: 返回结果 1111111--222222

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

thenAccept

示例代码:

public static void main(String[] args) throws IOException {CompletableFuture<Void> taskB = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).thenAccept(result -> {System.out.println("任务B获取到任务A结果: " + result);System.out.println("线程B名字: " + Thread.currentThread().getName());result = result + "--222222";System.out.println("result: " + result);});}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
任务B获取到任务A结果: 返回结果 1111111
线程B名字: main
result: 返回结果 1111111--222222

小结:thenAccept和thenApply使用方式一样,都是任务A和任务B的拼接,前置任务需要有返回结果,后置任务会接收前置任务的结果,不过后置任务没有返回值

thenAcceptAsync(自定义线程)

示例代码:

public static void main(String[] args) throws IOException {// 自定义线程池ExecutorService executor = Executors.newFixedThreadPool(10);// 开启异步(创建线程执行任务),有返回值CompletableFuture<Void> taskB = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).thenAcceptAsync(result -> { // 拼接异步任务,指定自定义线程池System.out.println("任务B获取到任务A结果: " + result);System.out.println("线程B名字: " + Thread.currentThread().getName());result = result + "--222222";System.out.println("result: " + result);}, executor);}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
任务B获取到任务A结果: 返回结果 1111111
线程B名字: pool-1-thread-1
result: 返回结果 1111111--222222

小结:thenAcceptAsync和thenApplyAsync使用方式一样,都是任务A和任务B的拼接,前置任务需要有返回结果,后置任务会接收前置任务的结果,不过后置任务没有返回值

thenRun

示例代码:

public static void main(String[] args) throws IOException {// 开启异步(创建线程执行任务),没有返回值CompletableFuture<Void> taskB = CompletableFuture.runAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());}).thenRun(() -> { // 拼接异步任务,不获取前面任务的返回值System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());});}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 222222
线程B名字: main

小结:用法和thenApply,thenAccept一样,都是任务A和任务B的拼接,前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也没有返回结果。

thenRunAsync(自定义线程)

示例代码:

public static void main(String[] args) throws IOException {// 自定义线程池ExecutorService executor = Executors.newFixedThreadPool(10);// 开启异步(创建线程执行任务),没有返回值CompletableFuture<Void> taskB = CompletableFuture.runAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());}).thenRunAsync(() -> { // 拼接异步任务,不获取前面任务的返回值System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());}, executor);}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 222222
线程B名字: pool-1-thread-1

小结:用法和thenApplyAsync,thenAcceptAsync一样,都是任务A和任务B的拼接,前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也没有返回结果。

thenCombine

示例代码:

public static void main(String[] args) {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).thenCombine(CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());return "返回结果 2222222";}), (r1, r2) -> {System.out.println("执行了吗 3333333");System.out.println("线程C名字: " + Thread.currentThread().getName());return r1 + r2;});String result = future.join();System.out.println("线程C结果: " + result);}

运行结果:

执行了吗 111111
执行了吗 222222
线程B名字: ForkJoinPool.commonPool-worker-2
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 3333333
线程C名字: main
线程C结果: 返回结果 1111111返回结果 2222222

小结:如有任务A,任务B,任务C,任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务需要有返回结果,后置任务接收前置任务的结果,有返回值

thenAcceptBoth

示例代码:

public static void main(String[] args) {CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());return "返回结果 2222222";}), (r1, r2) -> {System.out.println("执行了吗 3333333");System.out.println("线程C名字: " + Thread.currentThread().getName());String result = r2 + r1;System.out.println("线程C结果: " + result);});}

运行结果:

执行了吗 111111
执行了吗 222222
线程B名字: ForkJoinPool.commonPool-worker-2
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 3333333
线程C名字: main
线程C结果: 返回结果 2222222返回结果 1111111

小结:如有任务A,任务B,任务C,任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务需要有返回结果,后置任务接收前置任务的结果,没有返回值

runAfterBoth

示例代码:

public static void main(String[] args) {CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());return "返回结果 1111111";}).runAfterBoth(CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());return "返回结果 2222222";}), () -> {System.out.println("执行了吗 3333333");System.out.println("线程C名字: " + Thread.currentThread().getName());});}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 222222
线程B名字: ForkJoinPool.commonPool-worker-1
执行了吗 3333333
线程C名字: main

小结:如有任务A,任务B,任务C。任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C,当前方式前置任务不需要有返回结果,后置任务不会接收前置任务的结果,没有返回值

allOf

示例代码1:

public static void main(String[] args) throws IOException {CompletableFuture.allOf(CompletableFuture.runAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 111111");}),CompletableFuture.runAsync(() -> {System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 222222");}),CompletableFuture.runAsync(() -> {System.out.println("执行了吗 33333333");System.out.println("线程C名字: " + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 3333333");})).thenRun(() -> {System.out.println("执行了吗 44444444");System.out.println("线程D名字: " + Thread.currentThread().getName());});}

运行结果1:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 222222
线程B名字: ForkJoinPool.commonPool-worker-2
执行了吗 33333333
线程C名字: ForkJoinPool.commonPool-worker-3
执行了吗 44444444
线程D名字: main

示例代码2:

public static void main(String[] args) throws ExecutionException, InterruptedException {List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);// 任务的返回值List<CompletableFuture<Integer>> futures = nums.stream()// 任务提交 带返回值类型.map(value -> CompletableFuture.supplyAsync(() -> {System.out.println("任务名称: " + Thread.currentThread().getName() + " value: " + value);return value + 1;})).collect(Collectors.toList());// 等 allOf 里面的任务都执行完成之后在执行thenApplyAsync后面拼接的任务// 注意:allOf本身没有返回值,但是后面拼接的任务有返回值,此返回值取得是后面拼接任务的返回值CompletableFuture<Integer> sumFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApplyAsync(v -> {// 拼接任务,处理异步计算任务完成后,将它们的结果进行合并int sum = futures.stream().mapToInt(CompletableFuture::join).sum();return sum;});// 阻塞获取结果int sum = sumFuture.join();System.out.println(sum);// 如果不阻塞获取结果,则异步任务执行不影响main方法执行,使用join阻塞获取结果则会等待上述任务执行结束才会执行后面流程System.out.println("11111111111111111111");System.out.println("11111111111111111111");System.out.println("11111111111111111111");}

运行结果2:

任务名称: ForkJoinPool.commonPool-worker-1 value: 1
任务名称: ForkJoinPool.commonPool-worker-1 value: 3
任务名称: ForkJoinPool.commonPool-worker-2 value: 2
任务名称: ForkJoinPool.commonPool-worker-1 value: 4
任务名称: ForkJoinPool.commonPool-worker-1 value: 6
任务名称: ForkJoinPool.commonPool-worker-1 value: 7
任务名称: ForkJoinPool.commonPool-worker-2 value: 8
任务名称: ForkJoinPool.commonPool-worker-1 value: 9
任务名称: ForkJoinPool.commonPool-worker-2 value: 10
任务名称: ForkJoinPool.commonPool-worker-3 value: 5
65
11111111111111111111
11111111111111111111
11111111111111111111

小结:让内部编写多个CompletableFuture的任务,多个任务都执行完后,才会继续执行你后续拼接的任务,返回的CompletableFuture是Void,没有返回结果。

anyOf

示例代码:

public static void main(String[] args) throws IOException {CompletableFuture.anyOf(CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 111111");System.out.println("线程A名字: " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 111111");return "线程A";}),CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 222222");System.out.println("线程B名字: " + Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 222222");return "线程B";}),CompletableFuture.supplyAsync(() -> {System.out.println("执行了吗 33333333");System.out.println("线程C名字: " + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("执行结束了吗 3333333");return "线程C";})).thenAccept(r -> {System.out.println("执行了吗 4444444");System.out.println("线程D名字: " + Thread.currentThread().getName());System.out.println("result: " + r);});}

运行结果:

执行了吗 111111
线程A名字: ForkJoinPool.commonPool-worker-1
执行了吗 222222
线程B名字: ForkJoinPool.commonPool-worker-2
执行了吗 33333333
执行了吗 4444444
线程D名字: main

小结:让内部编写多个CompletableFuture的任务,只要有一个任务执行完毕就继续执行后续,最先执行完的任务做作为返回结果的入参。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/890167.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不需要服务器,使用netlify快速部署自己的网站

Netlify简介 1.1 Netlify的功能与特点 Netlify 是一个功能强大的静态网站托管平台&#xff0c;它不仅提供了简单的网站部署功能&#xff0c;还集成了许多现代化的开发工具和服务&#xff0c;帮助开发者更高效地构建、部署和管理网站。Netlify 的核心功能包括&#xff1a; 自动…

简单工厂模式和策略模式的异同

文章目录 简单工厂模式和策略模式的异同相同点&#xff1a;不同点&#xff1a;目的&#xff1a;结构&#xff1a; C 代码示例简单工厂模式示例&#xff08;以创建图形对象为例&#xff09;策略模式示例&#xff08;以计算价格折扣策略为例&#xff09;UML区别 简单工厂模式和策…

Unity动态读取外部图片转Texture2D,内存过大问题解决方案

问题描述 加载原始图片2.63M的图片,分辨率为3023*4032,占用内存108.5M 加载原始图片12.6 M的图片,分辨率为6000*8000,占用内存427.2M 太恐怖了吧 解决方案 1.加载完图片,等比缩放,宽高改为1024或者512以下 1024占用5.2M,512占用1.3M,相比小了很多 2.原始Texture2…

linux-----进程及基本操作

进程的基本概念 定义&#xff1a;在Linux系统中&#xff0c;进程是正在执行的一个程序实例&#xff0c;它是资源分配和调度的基本单位。每个进程都有自己独立的地址空间、数据段、代码段、栈以及一组系统资源&#xff08;如文件描述符、内存等&#xff09;。进程的组成部分&am…

ArkTs组件的学习

一. AlphabetIndexer 可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件 参数名类型必填说明arrayValueArray<string>是字母索引字符串数组&#xff0c;不可设置为空selectednumber是初始选中项索引值若超出索引值范围则取默认值0 class Lxr{tImg:Resource…

51c自动驾驶~合集42

我自己的原文哦~ https://blog.51cto.com/whaosoft/12888355 #DriveMM 六大数据集全部SOTA&#xff01;最新DriveMM&#xff1a;自动驾驶一体化多模态大模型&#xff08;美团&中山大学&#xff09; 近年来&#xff0c;视觉-语言数据和模型在自动驾驶领域引起了广泛关注…

Linux限制root 用户的远程登录(安全要求)

前言&#xff1a;现在基本用户主机都不允许使用root来操作&#xff0c;所以本文通过创建新用户&#xff0c;并限制root用户的ssh来解决这个问题 1. 创建新账户 aingo 首先&#xff0c;使用 root 账户登录系统。 sudo useradd aingo设置 aingo 账户密码&#xff1a; sudo pa…

前端学习笔记-Vue篇-04

4 Vue中的ajax 4.1 解决开发环境Ajax跨域问题 vue脚手架配置代理 配置参考 | Vue CLI方法一&#xff1a;在vue.config.js中添加如下配置: module.exports {devServer: {proxy: http://localhost:4000} } 说明: 1.优点:配置简单&#xff0c;请求资源时直接发给前端(8080)即…

【优选算法篇】位运算小课堂:从入门到精通的奇妙之旅(上篇)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

【Linux】重启系统后开不开机(内核模块丢失问题)

问题 重启后开不开机报错如下&#xff1a; FAILED failed to start load kernel moduiles 可以看到提示module dm_mod not found 缺少了dm_mod 在内核module目录中 reboot重启可以看到这个现象&#xff1a; 可以看到重启启动磁盘&#xff0c;加载不到root 原因 dm_mod模块…

【现代服务端架构】传统服务器 对比 Serverless

在现代开发中&#xff0c;选择合适的架构是至关重要的。两种非常常见的架构模式分别是 传统服务器架构 和 Serverless。它们各有优缺点&#xff0c;适合不同的应用场景。今天&#xff0c;我就带大家一起对比这两种架构&#xff0c;看看它们的差异&#xff0c;并且帮助你选择最适…

搭建Tomcat(四)---Servlet容器

目录 引入 Servlet容器 一、优化MyTomcat ①先将MyTomcat的main函数搬过来&#xff1a; ②将getClass()函数搬过来 ③创建容器 ④连接ServletConfigMapping和MyTomcat 连接&#xff1a; ⑤完整的ServletConfigMapping和MyTomcat方法&#xff1a; a.ServletConfigMappin…

WPF DataTemplate 数据模板

DataTemplate 顾名思义&#xff0c;数据模板&#xff0c;在 wpf 中使用非常频繁。 它一般用在带有 DataTemplate 依赖属性的控件中&#xff0c;如 ContentControl、集合控件 ListBox、ItemsControl 、TabControls 等。 1. 非集合控件中使用 <UserControl.Resources>&l…

Python中exifread库使用

目录 简要介绍 库的安装 使用案例 常见问题 简要介绍 exifread 是一个用于读取图像文件 EXIF 元数据的 Python 库&#xff0c;能够提取图片的隐藏信息&#xff0c;包括经纬度、拍摄时间等信息。 库的安装 使用exifread库首先要确保已经安装 pip install exifread 使用…

clickhouse-数据库引擎

1、数据库引擎和表引擎 数据库引擎默认是Ordinary&#xff0c;在这种数据库下面的表可以是任意类型引擎。 生产环境中常用的表引擎是MergeTree系列&#xff0c;也是官方主推的引擎。 MergeTree是基础引擎&#xff0c;有主键索引、数据分区、数据副本、数据采样、删除和修改等功…

网络安全(3)_安全套接字层SSL

4. 安全套接字层 4.1 安全套接字层&#xff08;SSL&#xff09;和传输层安全&#xff08;TLS&#xff09; &#xff08;1&#xff09;SSL/TLS提供的安全服务 ①SSL服务器鉴别&#xff0c;允许用户证实服务器的身份。支持SSL的客户端通过验证来自服务器的证书&#xff0c;来鉴别…

Starfish 因子开发管理平台快速上手:如何完成策略编写与回测

DolphinDB 开发的因子开发管理平台 Starfish 围绕量化投研的因子、策略开发阶段设计&#xff0c;为用户提供了一个从数据管理、因子研究到策略回测的完整解决方案。 因子平台的回测引擎提供了多个关键的事件函数&#xff0c;涵盖策略初始化、每日盘前和盘后回调、逐笔、快照和…

排序算法(3)——归并排序、计数排序

目录 1. 归并排序 1.1 递归实现 1.2 非递归实现 1.3 归并排序特性总结 2. 计数排序 代码实现 3. 总结 1. 归并排序 基本思想&#xff1a; 归并排序&#xff08;merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff0…

GIN

gin是什么 Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API&#xff0c;但性能比 Martini 快 40 倍。如果你需要极好的性能&#xff0c;使用 Gin 吧。 特点&#xff1a;gin是golang的net/http库封装的web框架&#xff0c;api友好&#xff0c;注…

基于asp.net游乐园管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…