1 简介

  Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,调用方的线程就被阻塞,直到目标线程的call方法结束并返回结果。

  线程的实现方式有几种方式,继承Thread类,实现Runnable接口,线程池,callable这种方式。

  callable和Runnable的区别是callable可以有返回值,也可以抛出异常的特性,而Runnable没有。

  注意callable可以有返回值,也可以抛出异常这点很关键。
  很多时候我们让多线程去帮我们处理事情,是需要拿到返回值的,有了异常也可以处理,比如某宝的APP页面,一个页面展示3个块,而每个块展示的信息从后端获取的接口都不一样,那么是让前端调后端3次接口吗?
  后端可以把3个块的信息,包装成一个接口,全部返回,那么问题来了,后端调用3个接口,比如第一个接口需要1秒,第二个需要2秒,第三个需要3秒,那么包装的这个接口响应时间最少6秒,怎么解决这个问题呢,可以用多线程来帮我们解决。
  启动3个线程,每个线程去调用一个接口,那么3个线程一共执行完的时间就是最慢的那个线程的执行时间,这样接口的响应时间就变成了3秒,一下节省了一半的时间。
  那么问题来了,线程如何把执行的业务代码的结果返回来呢?这时候就用到callable了

2 Future

2.1 源码

//它定义了对线程Callable执行的管理,包括执行过程中取消,判断是否取消了,是否执行完成,获取放回结果
public interface Future<V> {

  //取消Callable的执行,当Callable还没有完成时
boolean cancel(boolean mayInterruptIfRunning);   //是否取消了
boolean isCancelled();   //是否执行完成了
boolean isDone();   //获取返回结果
V get() throws InterruptedException, ExecutionException;   //获取返回结果,若超过指定时间还没有获取到(还在执行中),直接不获取了
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask 实现了Future

    
//FutureTask 的构造函数,传入一个Callable
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}

2.2 示例1

使用FutureTask 来执行一个线程任务

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

        System.out.println("我在写作业");

        FutureTask f = new FutureTask(()->{
System.out.println( "发现笔快没油了,叫" + Thread.currentThread().getName() + "帮我去买笔【是否是守护线程" + Thread.currentThread().isDaemon() + "】"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "买回来了");
return "晨光牌中性笔";
}); Thread t = new Thread(f,"弟弟");
t.start(); System.out.println("奋笔疾书中aaa");
}
}

执行结果,看到是主线程执行完成了,而FutureTask异步线程还在继续执行。异步线程是用户线程

我在写作业
奋笔疾书中aaa
发现笔快没油了,叫弟弟帮我去买笔【是否是守护线程false】
弟弟买回来了

2.2 示例2

  使用FutureTask 来执行一个线程任务,并且获得执行结果

  我写作业也,发现快没有了,叫弟弟去给我买笔,我继续写作业,等弟弟买回来了,我换上新买的笔继续写作业。

  达到在写作业的同时去买笔,还可以拿到新买的笔的效果

public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

        System.out.println("我在写作业");

        FutureTask f = new FutureTask(()->{
System.out.println( "发现笔快没油了,叫" + Thread.currentThread().getName() + "帮我去买笔"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "买回来了");
return "晨光牌中性笔";
}); Thread t = new Thread(f,"弟弟");
t.start(); System.out.println("奋笔疾书中aaa"); Object o = f.get(); System.out.println("奋笔疾书中bbb"); System.out.println("换弟弟给我新买的:" + o + "写作业");
}
}

执行结果,发现在Object o = f.get();出发生阻塞了,get()后面的代码,主线程是等待FutureTask异步线程执行完成后继续执行的,所以说get方法会造成阻塞

我在写作业
奋笔疾书中aaa
发现笔快没油了,叫弟弟帮我去买笔
弟弟买回来了
奋笔疾书中bbb
换弟弟给我新买的:晨光牌中性笔写作业

2.3 示例2(不见不散)

  get方法会找出阻塞

  把Thread.sleep(500);时间变为5000,再次执行

  清楚的发现System.out.println("奋笔疾书中bbb");这条语句被阻塞了,它是等笔买回来了,才执行的。也就是f.get()这个方法会造成阻塞

2.4 示例3(过时不候)

  get方法传入等待时间

public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

        System.out.println("我在写作业");

        FutureTask f = new FutureTask(()->{
System.out.println( "发现笔快没油了,叫" + Thread.currentThread().getName() + "帮我去买笔"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "买回来了");
return "晨光牌中性笔";
}); Thread t = new Thread(f,"弟弟");
t.start(); System.out.println("奋笔疾书中aaa"); Object o = f.get(2, TimeUnit.SECONDS); System.out.println("奋笔疾书中bbb"); System.out.println("换弟弟给我新买的:" + o + "写作业");
}
}

执行结果,超过2秒,没有获取到结果,主线程直接报错

我在写作业
奋笔疾书中aaa
发现笔快没油了,叫弟弟帮我去买笔
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at com.ruoyi.weixin.user.SuoTest.FutureTest.main(FutureTest.java:28)
弟弟买回来了

2.5 示例4

  使用isDone判断是否完成,这样子线程不会阻塞(可以先做点别的),虽然也是需要等待执行完成才能向下继续执行

public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

        System.out.println("我在写作业");

        FutureTask f = new FutureTask(()->{
System.out.println( "发现笔快没油了,叫" + Thread.currentThread().getName() + "帮我去买笔"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "买回来了");
return "晨光牌中性笔";
}); Thread t = new Thread(f,"弟弟");
t.start(); System.out.println("奋笔疾书中aaa"); while (true){
if(f.isDone()){
Object o = f.get();
System.out.println("换弟弟给我新买的:" + o + "写作业");
break;
}else{
System.out.println("看了眼门口,弟弟还没回来,继续写作业");
Thread.sleep(500);
} }
}
}

执行结果

我在写作业
奋笔疾书中aaa
看了眼门口,弟弟还没回来,继续写作业
发现笔快没油了,叫弟弟帮我去买笔
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
看了眼门口,弟弟还没回来,继续写作业
弟弟买回来了
换弟弟给我新买的:晨光牌中性笔写作业 Process finished with exit code 0

2.6 小结

  1)通过FutureTask 可以创建异步任务,并且可以获得执行结果,而且可以进行异常处理

  2)FutureTask异步线程是用户线程,不会随着主线程的结束而结束

  3)调用get方法会造成阻塞(是一个比较大的缺点)

  4)get(等待时间),超时了,主线程直接报错

  5)可以通过isDone方法判断异步任务是否完成

3  CompletableFuture

3.1 简介

   CompletableFuture是FutureTas的升级版,FutureTask能做的它能做,FutureTask不能做的它也能做。

   CompletableFuture实现了CompletionStage接口和Future接口,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利

3.2 继承关系图

3.3 异步任务方法supplyAsync / runAsync

  supplyAsync表示创建带返回值的异步任务的,runAsync表示创建无返回值的异步任务

3.3.1 示例supplyAsync

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 1;
});
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,System.out.println(Thread.currentThread().getName() + "任务执行结束");这一句话没有打印,因为CompletableFuture.supplyAsync异步线程是守护线程,主线程执行结束后,守护线程会立即结束。

main执行开始
main执行结束
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true

3.3.2 runAsync方法

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "任务执行结束");
});
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,System.out.println(Thread.currentThread().getName() + "任务执行结束");这一句话没有打印,因为CompletableFuture.supplyAsync异步线程是守护线程,主线程执行结束后,守护线程会立即结束

main执行开始
main执行结束
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true

3.3.3 示例-传入参数pool

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "任务执行结束");
},pool);
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果

main执行开始
main执行结束
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true

注意,这里runAsync传入了一个参数-线程池。supplyAsync和runAsync都可以传入一个线程池作为参数。不传时使用默认的线程池,传入了就使用传入的线程池

3.3.4 小结

  1)supplyAsync 和 runAsync都会发起一个异步任务

  2)supplyAsync 和 runAsync线程都是守护线程

  3)supplyAsync 和 runAsync都可以传入一个线程池,传入了就使用传入的线程池

  3)supplyAsync有返回值,runAsync没有返回值

3.4 异步回调方法 (thenApplyAsync/thenRunAsync/thenAcceptAsync)和(thenApply/thenRun/thenAccept)

  thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中

3.4.1 示例thenApplyAsync

接收任务执行完成返回的值做为参数,同时自己也有返回值

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenApplyAsync((jobresult)->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync");
return jobresult*2;
});
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,发现thenApplyAsync回调中的线程和supplyAsync任务的线程不是从同一个线程池取的。因为supplyAsync传入了pool参数,使用的是我们创建的线程池。thenApplyAsync没有传入(也可以传入线程池参数),使用的是默认的线程池

同时thenApplyAsync回调线程也是守护线程,主线程结束了,它就立即结束

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main执行结束
ForkJoinPool.commonPool-worker-1是否是守护线程-true

3.4.2  示例-传入参数pool

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenRunAsync(()->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync"); },pool);
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,任务和回调使用的都是自己创建的线程池

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main执行结束
ForkJoinPool-1-worker-1是否是守护线程-true

3.4.3 thenRunAsync

  不用参数,也没有返回值

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenRunAsync(()->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync"); });
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,出了不接收参数无返回值外,其他的和thenApplyAsync一样

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main执行结束
ForkJoinPool.commonPool-worker-1是否是守护线程-true

3.4.4 示例

不接受参数,有返回值thenAcceptAsync

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenAcceptAsync((a)->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync"); });
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main执行结束
ForkJoinPool.commonPool-worker-1是否是守护线程-true

3.4.5 示例thenApply

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenApply((a)->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync");
return a; });
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,它不能传入pool作为参数。注意到,它里面用的线程是main主线程

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main是否是守护线程-false
supplyAsync的任务完成了,传入执行结果回调thenApplyAsync
main执行结束

3.4.6 示例thenRun

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenRun(()->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync"); });
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,它不能传入pool作为参数。它里面用的线程是main主线程

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main是否是守护线程-false
supplyAsync的任务完成了,传入执行结果回调thenApplyAsync
main执行结束

3.4.7 示例thenAccept

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        ForkJoinPool pool = new ForkJoinPool();

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
System.out.println(Thread.currentThread().getName() + "任务执行结束");
return 111;
},pool).thenAccept((a)->{
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("supplyAsync的任务完成了,传入执行结果回调thenApplyAsync"); });
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,它不能传入pool作为参数。它里面用的线程是main主线程

main执行开始
ForkJoinPool-1-worker-1执行1
ForkJoinPool-1-worker-1是否是守护线程-true
ForkJoinPool-1-worker-1任务执行结束
main是否是守护线程-false
supplyAsync的任务完成了,传入执行结果回调thenApplyAsync
main执行结束

3.4.8 小结

  1)六个方法都是任务执行完成后的回调方法

  2)thenApplyAsync和 thenRunAsync和thenAcceptAsync

    都可以传入一个线程池,传入了就使用传入的线程池,不传入使用默认的线程池

    线程都是守护线程,在主线程结束后,会结束。

    thenApplyAsync接收一个参数,有一个返回值。thenRunAsync没有参数,也没有返回值,thenAcceptAsync有一个参数,没有返回值

  3)thenApply和 thenRun和thenAccept(和前三个的区别)

    没有pool作为参数,使用的是还未回收的线程(有可能是上个任务的线程,有可能是主线程)

3.5 whenComplete方法

  whenComplete是当某个任务执行完成后执行的回调方法,会将执行结果或者执行期间抛出的异常传递给回调方法

3.5.1 示例1(whenComplete)

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

          CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).whenComplete((v,e)->{ //第一个参数是返回值 第二个参数是异常
System.out.println(Thread.currentThread().getName() + "执行2");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
if(e == null){
System.out.println(v);
}
}).exceptionally((e)->{
e.printStackTrace();
return 0;
}); //Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,发现whenComplete里面的没有执行,因为主线程执行结束了,守护线程也就结束了,CompletableFuture创建的是守护线程

   main执行开始
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true
main执行结束

把//Thread.sleep(3000);的注释解开,执行结果,whenComplete里面的执行了,因为主线程等待了3秒钟

  main执行开始
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true
ForkJoinPool.commonPool-worker-1执行2
ForkJoinPool.commonPool-worker-1是否是守护线程-true
1
main执行结束

3.5.2 示例2(whenComplete+get)

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).whenComplete((v,e)->{
System.out.println(Thread.currentThread().getName() + "执行2");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
if(e == null){
System.out.println(v);
}
}).exceptionally((e)->{
e.printStackTrace();
return 0;
}).get();
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,whenComplete正常执行,因为get会导致主线程阻塞

  main执行开始
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true
ForkJoinPool.commonPool-worker-1执行2
ForkJoinPool.commonPool-worker-1是否是守护线程-true
1
main执行结束

3.5.3 示例3(whenComplete+get(超时时间))

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).whenComplete((v,e)->{
System.out.println(Thread.currentThread().getName() + "执行2");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
if(e == null){
System.out.println(v);
}
}).exceptionally((e)->{
e.printStackTrace();
return 0;
}).get(1, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,超时报错,结束

  main执行开始
  ForkJoinPool.commonPool-worker-1执行1
  ForkJoinPool.commonPool-worker-1是否是守护线程-true
  Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1771)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915)
at com.ruoyi.weixin.user.MyTest.JucTest.CompletableFutureTest2.main(CompletableFutureTest2.java:40)

3.5.4 示例4(whenComplete+join(超时时间))

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        System.out.println(Thread.currentThread().getName() + "执行开始");

        CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "执行1");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).whenComplete((v,e)->{
System.out.println(Thread.currentThread().getName() + "执行2");
System.out.println( Thread.currentThread().getName() + "是否是守护线程-" + Thread.currentThread().isDaemon());
if(e == null){
System.out.println(v);
}
}).exceptionally((e)->{
e.printStackTrace();
return 0;
}).join();
System.out.println(Thread.currentThread().getName() + "执行结束");
}

执行结果,join和get方法一样,都是去获取结果,都会造成阻塞

main执行开始
ForkJoinPool.commonPool-worker-1执行1
ForkJoinPool.commonPool-worker-1是否是守护线程-true
ForkJoinPool.commonPool-worker-1执行2
ForkJoinPool.commonPool-worker-1是否是守护线程-true
1
main执行结束

 3.5.5 小结

  1)whenComplete是任务执行完成后的回调

  2)whenComplete是守护线程

3)whenComplete不会阻塞主线程

4)get方法会阻塞主线程

  5)get(等待时间),超时会直接报错

3.6 CompletableFuture的优势示例

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Book { private String name; private Double price; }
public class CompletableFutureTest6 {

    public static void main(String[] args) {

        List<String> li = new ArrayList<>();
li.add("京东");
li.add("淘宝");
li.add("当当");
li.add("唯品会");
li.add("拼多多"); if(true){//一个接一个去拿
long l1 = System.currentTimeMillis();
li.stream().map(shop -> String.format("%s 的价格是:%f",shop ,getBook(shop,"问道").getPrice())).
collect(Collectors.toList()).
stream().
forEach(System.out::println);; System.out.println(System.currentTimeMillis()-l1);
}
if(true){//异步多个同时去拿
long l1 = System.currentTimeMillis();
li.stream().
map(shop-> CompletableFuture.supplyAsync(()->String.format("%s 的价格是:%f",shop ,getBook(shop,"问道").getPrice()))).
collect(Collectors.toList()).
stream().
map(CompletableFuture::join).forEach(System.out::println);
System.out.println(System.currentTimeMillis()-l1);
} } public static Book getBook(String shopname,String bookname){
System.out.println(Thread.currentThread().getName() + ":到" + shopname + "取书-" + bookname);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} Book b = new Book(bookname, ThreadLocalRandom.current().nextDouble());
return b; } }

执行结果,发现使用CompletableFuture异步获取大大缩短了执行时间。因为它是多个线程同时去获取的。

main:到京东取书-问道
main:到淘宝取书-问道
main:到当当取书-问道
main:到唯品会取书-问道
main:到拼多多取书-问道
京东 的价格是:0.316333
淘宝 的价格是:0.593280
当当 的价格是:0.761204
唯品会 的价格是:0.677869
拼多多 的价格是:0.686680
5070
ForkJoinPool.commonPool-worker-1:到京东取书-问道
ForkJoinPool.commonPool-worker-2:到淘宝取书-问道
ForkJoinPool.commonPool-worker-3:到当当取书-问道
ForkJoinPool.commonPool-worker-4:到唯品会取书-问道
ForkJoinPool.commonPool-worker-5:到拼多多取书-问道
京东 的价格是:0.507088
淘宝 的价格是:0.402202
当当 的价格是:0.537196
唯品会 的价格是:0.261127
拼多多 的价格是:0.293971
1006

3.7 CompletableFuture常用方法简介

3.7.1 获取结果

  1)public T    get(),获取结果,会造成阻塞

  2)public T    get(long timeout, TimeUnit unit),获取结果,超时报错

  3)public T    getNow(T valueIfAbsent),立即获取结果不会造成阻塞。调用该方法时,如果任务已经执行完了,返回任务的结果,如果任务没有执行完,获取到的是预设的值valueIfAbsent

  4)public T    join(),获取结果,会造成阻塞

3.7.2 主动结束任务

  1)public boolean complete(T value) 主动去结束任务。调用本方法时,如果任务已经结束,返回false。如果任务没有结束,立即结束任务,返回true。且把参数value作为任务的返回值

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
return 111;
});
Thread.sleep(2100);
System.out.println(integerCompletableFuture.complete(222) + "|" + integerCompletableFuture.get());
}

执行结果。执行complete方法时,任务已结束,返回的是false,get获取到的是任务的返回值111

false|111
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
return 111;
}); System.out.println(integerCompletableFuture.complete(222) + "|" + integerCompletableFuture.get());
}

执行结果。调用complete方法时,任务还在执行中,立即结束任务,返回为true。get获取到的是complete传入的预设值222

true|222

3.7.3 对计算结果进行处理

3.7.3.1 thenApplyAsync和 thenRunAsync和thenAcceptAsync和thenApply和 thenRun和thenAccept(再回顾下这六个方法)

  1)六个方法都是任务执行完成后的回调方法

  2)thenApplyAsync和 thenRunAsync和thenAcceptAsync

    都可以传入一个线程池,传入了就使用传入的线程池,不传入使用默认的线程池

    线程都是守护线程,在主线程结束后,会结束。

    thenApplyAsync接收一个参数,有一个返回值。thenRunAsync没有参数,也没有返回值,thenAcceptAsync有一个参数,没有返回值

  3)thenApply和 thenRun和thenAccept(和前三个的区别)

    没有pool作为参数,使用的是还未回收的线程(有可能是上个任务的线程,有可能是主线程)

  带上Async和没有Async的区别就是,带上Async交给线程池管理,不带Async使用的是还未回收的线程(有可能是上个任务的线程,有可能是主线程)

3.7.3.2 handle

  和thenApply差不多,不过它执行过程中出现异常,还可以继续执行下去

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
return 111;
}).thenApply((v)->{
System.out.println("第一个thenApply:" + v);
int i = 1/0;
return v * 2;
}).thenApply((v)->{
System.out.println("第二个thenApply:" + v);
return v * 3;
}).exceptionally(e->{
e.printStackTrace();
return -1;
});
}

执行结果,第一个thenApply出错后,第二个thenApply没有执行,直接来到exceptionally异常处理

第一个thenApply:111
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.uniApply(CompletableFuture.java:604)
at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:614)
at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1983)
at com.ruoyi.weixin.user.MyTest.JucTest.CompletableFutureTest19.main(CompletableFutureTest19.java:20)
Caused by: java.lang.ArithmeticException: / by zero
at com.ruoyi.weixin.user.MyTest.JucTest.CompletableFutureTest19.lambda$main$1(CompletableFutureTest19.java:22)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
... 3 more
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
return 111;
}).handle((v,e)->{
System.out.println("第一个handle:" + v);
int i = 1/0;
return v * 2;
}).handle((v,e)->{
System.out.println("第二个handle:" + v);
return v * 3;
}).exceptionally(e->{
e.printStackTrace();
return -1;
});
}

执行结果,第一个handle出错后,返回null值,第二个handle继续执行,最后才回到exceptionally异常梳理

第一个handle:111
第二个handle:null
java.util.concurrent.CompletionException: java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:824)
at java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:834)
at java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2155)
at com.ruoyi.weixin.user.MyTest.JucTest.CompletableFutureTest18.main(CompletableFutureTest18.java:24)
Caused by: java.lang.NullPointerException
at com.ruoyi.weixin.user.MyTest.JucTest.CompletableFutureTest18.lambda$main$2(CompletableFutureTest18.java:26)
at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)
... 3 more

3.7.3.3 thenCompose

  thenCompose允许将两个异步操作进行流水线,第一个操作完成时,将其结果作为参数传递给第二个操作

<U> CompletionStage<U> thenApply​(Function<? super T,? extends U> fn)

<U> CompletionStage<U> thenCompose​(Function<? super T,? extends CompletionStage<U>> fn)

可以看到,两个方法的返回值都是CompletionStage<U>,不同之处在于它们的传入参数fn.

  • 对于thenApplyfn函数是一个对一个已完成的stage或者说CompletableFuture的的返回值进行计算、操作;
  • 对于thenComposefn函数是对另一个CompletableFuture进行计算、操作。
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1执行");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("完成任务1执行");
return 100;
}).thenApply(num -> {
System.out.println("任务2执行");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("完成任务2执行");
return num + " to String";
}); CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务3执行");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("完成任务3执行");
return 100;
}).thenCompose(num -> CompletableFuture.supplyAsync(() -> {
System.out.println("任务4执行");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("完成任务4执行");
return num + " to String";
})); System.out.println(f1.join());
System.out.println(f2.join());
}

执行结果

  thenApply是任务1和任务2,任务2是在任务1完成后再执行的

  thenCompose的是任务3和任务4,任务4是在任务3完成后再执行的

  也就是说thenApply和thenCompose的任务都是顺序执行,后一个任务依赖前一个任务

任务1执行
任务3执行
完成任务1执行
任务2执行
完成任务3执行
任务4执行
完成任务2执行
100 to String
完成任务4执行
100 to String Process finished with exit code 0

3.7.4 竞争上岗applyToEither

  获取最先执行完的任务的结果,然后采取这个任务及之后任务的结果处理函数进行处理

static void completableFutureTe3() throws ExecutionException, InterruptedException {
String re = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行1号任务");
return "1号任务返回值";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行2号任务");
return " 2号任务返回值 ";
}), x -> x + "*对结果第一次处理*").applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行3号任务");
return "2号任务返回值";
}), y -> y + "*对结果第二次处理*").get();
System.out.println(re); }

执行结果

  任务1、2、3共三个任务同时执行,谁先完成就取谁的结果。任务一所需时间最短,所以取得任务一的返回值,然后再经过了两次处理

执行1号任务
1号任务返回值*对结果第一次处理**对结果第二次处理*

  把睡眠时间调整下,让第二个任务先执行完

 //几个任务同时执行,获取最先执行完的结果,再进行处理
static void completableFutureTe3() throws ExecutionException, InterruptedException {
String re = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行1号任务");
return "1号任务返回值";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行2号任务");
return " 2号任务返回值 ";
}), x -> x + "*对结果第一次处理*").applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行3号任务");
return "3号任务返回值";
}), y -> y + "*对结果第二次处理*").get();
System.out.println(re); }

执行结果

执行2号任务
2号任务返回值 *对结果第一次处理**对结果第二次处理*

把睡眠时间调整下,让第三个任务先执行完

static void completableFutureTe3() throws ExecutionException, InterruptedException {
String re = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行1号任务");
return "1号任务返回值";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行2号任务");
return " 2号任务返回值 ";
}), x -> x + "*对结果第一次处理*").applyToEither(CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("执行3号任务");
return "3号任务返回值";
}), y -> y + "*对结果第二次处理*").get();
System.out.println(re); }

执行结果

执行3号任务
3号任务返回值*对结果第二次处理*

注意,此时和上面两次有所不同,上面对结果都进行了两次处理,而这里只进行了一次。说明取得某个任务的结果后,只会采取这个任务及之后的结果处理函数对结果进行处理。因为这里取得的是第三个任务的结果,所以只采用了这个任务的结果处理函数进行处理

3.7.4 对计算结果进行合并thenCombine

3.7.4.1 示例

    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        Integer join = CompletableFuture.supplyAsync(() -> {
System.out.println("执行第一个任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成执行第一个任务");
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println("执行第二个任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成执行第二个任务");
return 20;
}), (a, b) -> {
return a + b;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println("执行第三个任务");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成执行第三个任务");
return 30;
}), (a, b) -> {
return a + b;
}).join();
System.out.println(join);
}
}

执行结果

  共三个任务同时执行,先把任务1和2的结果进行处理,再把处理后的结果和第三个任务的结果进行处理。

执行第一个任务
执行第二个任务
执行第三个任务
完成执行第一个任务
完成执行第三个任务
完成执行第二个任务
60

线程基础知识02-CompletableFuture的更多相关文章

  1. MongoDB基础知识 02

    MongoDB基础知识 02 6 数据类型 6.1 null : 表示空值或者不存在的字段 {"x":null} 6.2 布尔型 : 布尔类型只有两个值true和false {&q ...

  2. day03-MySQL基础知识02

    MySQL基础知识02 4.CRUD 数据库CRUD语句:增(create).删(delete).改(update).查(Retrieve) Insert 语句 (添加数据) Update 语句(更新 ...

  3. Java__线程---基础知识全面实战---坦克大战系列为例

    今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...

  4. java线程基础知识----线程与锁

    我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...

  5. java线程基础知识----线程基础知识

    不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...

  6. Windows核心编程 第六章 线程基础知识 (上)

    第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...

  7. Java并发之线程管理(线程基础知识)

    因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...

  8. Java线程基础知识(状态、共享与协作)

    1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...

  9. java线程基础知识----java daemon线程

    java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...

  10. java并发编程(一)----线程基础知识

    在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...

随机推荐

  1. elasticsearch聚合之bucket terms聚合

    目录 1. 背景 2. 前置条件 2.1 创建索引 2.2 准备数据 3. 各种聚合 3.1 统计人数最多的2个省 3.1.1 dsl 3.1.2 运行结果 3.2 统计人数最少的2个省 3.2.1 ...

  2. 支持 equals 相等的对象(可重复对象)作为 WeakHashMap 的 Key

    原文地址 代码地址 问题 长链接场景下通常有一个类似 Map<String, Set<Long>> 的结构,用来查找一个逻辑组内的哪些用户,String 类型的 Entry.k ...

  3. devexpress中searchLookUpEdit赋值不显示

    给searchLookUpEdit进行赋值的时候使用 string str="123"; searchLookUpEdit1.EditValue = str; 一直不显示或者显示为 ...

  4. typora使用CSDN图床

    前言 软件下载地址 typora 是一款好用的 markdown 文档编辑器. 我之前研究过CSDN上传图片的接口,前几天发现了Typora 这个好用的软件,可以自定义图床,因为我很多文章图片资源用的 ...

  5. 关于vlc"编解码器暂不支持: VLC 无法解码格式“MIDI” (MIDI Audio)"解决

    解决办法 sudo apt install vlc-plugin-fluidsynth

  6. 解决一个mysql报错

    问题描述 insert into btsync (key,title) values ('a','b'); ERROR 1064 (42000): You have an error in your ...

  7. 【2022-11-28】Docker部署搭建Gitlab

    一.环境准备 1. 准备一台虚拟机\或者购买服务器 2. 虚拟机硬件要求 2.1 内存不得少于4G,否则启动会报502错误,可自行百度解决,将虚拟机的swap分区调整为2G大小即可 2.2 CPU2核 ...

  8. Entity Framework Core 7中高效地进行批量数据插入

    因为之前的版本中,EF Core无法实现高效地批量插入.修改.删除数据,所以我开发了Zack.EFCore.Batch这个开源项目,比较受大家的欢迎,获得了400多个star. 从.NET 7开始,微 ...

  9. JavaScript入门④-万物皆对象:Object

    01.Object对象 Object 是 JavaScript 的一种 数据类型,它用于存储各种键值集合和更复杂的实体,是一组数据和功能的集合.JS中几乎所有对象都是继承自Object,Array.R ...

  10. Excel二维码图片生成器

    Excel二维码图片生成器 它可以将excel文件的数据,每行数据生成一张二维码图片,并保存到电脑.软件无需安装,解压后即可直接使用,无需联网,操作简便快捷. 软件下载地址:点此下载 步骤1:导入事先 ...