JUC第二讲-CompletableFuture

28 阅读8分钟

Future接口

图片.png Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后, 主线程就去做其他事情了,忙其它事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态。

主线程 不中断的情况下,新建一个异步线程去完成某个任务(耗时)

Future接口常用的实现类FutureTask

图片.png三个特点:多线程/有返回/异步任务

图片.png 实现了RunableFuture 接口 再通过构造注入 注入Callable

public class FutureTaskTest {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
      FutureTask<String> futureTask=new FutureTask<>(new MyThread2());

      Thread t1=new Thread(futureTask,"t1");
      t1.start();
      System.out.println(futureTask.get());
   }
}

class MyThread2 implements Callable<String>{

   @Override
   public String call() throws Exception {
      return "HELLO";
   }
}

Future编码实战和优缺点分析

future+线程池异步多线程任务配置,能显著提高程序的执行效率

public class FutureThreadPoolDemo {
   public static void main(String[] args) {
      //3个任务,目前开启多个线程来处理 耗时多少
      ExecutorService executorService= Executors.newFixedThreadPool(3);

      long startTime=System.currentTimeMillis();
      FutureTask<Integer> futureTask1=new FutureTask<>(()->{
         try {
            TimeUnit.SECONDS.sleep(4);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return 1;
      });
      FutureTask<Integer> futureTask2=new FutureTask<>(()->{
         try {
            TimeUnit.SECONDS.sleep(5);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return 1;
      });
      executorService.submit(futureTask1);
      executorService.submit(futureTask2);
      try {
         TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      }
      long endTime=System.currentTimeMillis();
      System.out.println(endTime-startTime);
      executorService.shutdown();
   }
   public static void m1() {
      //3个任务,目前一个线程main来处理 耗时多少

      try {
         TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      }
      try {
         TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      }
      try {
         TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      }

   }

}

缺点:

get()阻塞

   @Test
   public void test1() throws ExecutionException, InterruptedException {
      FutureTask<String> futureTask=new FutureTask<>(()->{
         System.out.println(Thread.currentThread().getName()+"-----come  in");
         TimeUnit.SECONDS.sleep(3);
         return "task over";
      });
      Thread t1=new Thread(futureTask,"t1");
      t1.start();
      System.out.println(futureTask.get());//阻塞状态
      System.out.println(Thread.currentThread().getName()+"执行自己的任务了");
   }

输出结果 t1-----come in task over main执行自己的任务了

调用get()方法会阻塞主线程 一般建议放在程序最后面

加入我不愿意等待很长事件,我希望过时不候,可以自动离开 futureTask.get(3,TimeUnit.SECONDS)设置最长等待时间

isDone()轮询

   @Test
   public void test2() throws ExecutionException, InterruptedException {
      FutureTask<String> futureTask=new FutureTask<>(()->{
         System.out.println(Thread.currentThread().getName()+"-----come  in");
         TimeUnit.SECONDS.sleep(4);
         System.out.println(Thread.currentThread().getName()+"-----执行完了");
         return "task over";
      });
      Thread t1=new Thread(futureTask,"t1");
      t1.start();

      System.out.println(Thread.currentThread().getName()+"执行自己的任务了");

      while (true){
         if(futureTask.isDone()){
            System.out.println(futureTask.get());
            break;
         }else {
            System.out.println("正在处理中");
         }
      }
   }

轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果 如果想要异步获取结果,通常都会以轮询的方式去获取结果 尽量不要阻塞

结论

Future对于结果的获取不太友好,要么阻塞,要么就会消耗过多资源 如果多个任务前后要互相依赖,Future就完成不了了 由此引入了CompletableFuture

CompletableFuture

CompletableFuture提供了一种观察者模式的类似机制,可以让任务执行完后通知监听的一方 图片.png 图片.png

·CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段 ·一个阶段的计算执行可以是一个Function,Consumer或者Runnable。.比如:Sage.thenApply(x->square(x).thenAccept (x -System.out.print(x)).thenRun(()->System.out.println()) ·一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

CompletableFuture 对象获取方法

  1. CompletableFuture.runAsync( Runnable runable) 无返回值
  2. CompletableFuture.supplyAsync(Supplier supplier ) 有返回值
    @Test
    public void test1() throws ExecutionException, InterruptedException {
        //未指定线程池,用默认线程池
        CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println(completableFuture.get());
    }

    @Test
    public void test2() throws ExecutionException, InterruptedException {
        ExecutorService threadPool= Executors.newFixedThreadPool(3);
        //指定线程池
        CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },threadPool);
        System.out.println(completableFuture.get());
        threadPool.shutdown();
    }

    @Test
    public void test3() throws ExecutionException, InterruptedException {
        ExecutorService threadPool= Executors.newFixedThreadPool(3);
        //指定线程池
        CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "ok";
        },threadPool);
        System.out.println(completableFuture.get());

        threadPool.shutdown();
    }

Code -通用演示,减少阻塞和轮询

图片.png

    @Test
    public void test5() throws ExecutionException, InterruptedException {
        ExecutorService threadPool= Executors.newFixedThreadPool(3);
        //指定线程池
        CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"------come in");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("三秒后----出结果");
            return "ok";
        },threadPool).whenComplete((v,e)->{
            if(e==null){
                System.out.println("------成功计算完成----"+v);
            }
        }).exceptionally(e->{
            e.printStackTrace();
            System.out.println("异常情况"+e.getCause()+e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName()+"先去忙其他任务");
        //监听任务是个守护线程,让主程序保持运行,才会让其执行完
        TimeUnit.SECONDS.sleep(4);


        threadPool.shutdown();
    }
    @Test
    public void test5() throws ExecutionException, InterruptedException {
        ExecutorService threadPool= Executors.newFixedThreadPool(3);
        //指定线程池
       try{
            CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName()+"------come in");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("三秒后----出结果");
                if(true){
                    throw new RuntimeException("cuowu");
                }
                return "ok";
            },threadPool).whenComplete((v,e)->{
                if(e==null){
                    System.out.println("------成功计算完成----"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println("异常情况"+e.getCause()+e.getMessage());
                return null;
            });

            System.out.println(Thread.currentThread().getName()+"先去忙其他任务");
           TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
           e.printStackTrace();
       }finally {
           threadPool.shutdown();
       }



    }

CompletableFuture的优点

  1. 异步任务结束后,会自动调用某个对象的方法
  2. 主线程任务设置好之后,不用关系异步任务的执行
  3. 异步任务执行错误之后,也能抛出异常执行相应的方法

案例实战

函数式接口

Runnable
Function
Consumer/BiConsumer Supplier 图片.png

Jion 和 Get方法区别

_System.out.println(completableFuture.get()); _System.out.println(completableFuture.join());

get()在编译时候要抛出异常,join()不用

单线程案例

public class CompletableFutureMall {
   static List<NetMall> list= Arrays.asList(
           new NetMall("JD"),
           new NetMall("TB"),
           new NetMall("PDD")
   );
   public static  List<String> getPrice( List<NetMall> list,String productName){
       return list.stream().map((netMall)->{
         try {
            return String.format(productName+"in %s price is %.2f",netMall.getNetMallName(),netMall.calcPrice(productName));
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }).collect(Collectors.toList());
   }
   public static void main(String[] args) {
      long startTime=System.currentTimeMillis();
      List<String> mysql = getPrice(list, "mysql");
      for (String s : mysql) {
         System.out.println(s);
      }
      long endTime=System.currentTimeMillis();
      System.out.println("查询花费时间:"+(endTime-startTime));

   }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class NetMall{
   private String netMallName;

   public  double calcPrice(String productName) throws InterruptedException {
      TimeUnit.SECONDS.sleep(1);
      //模拟书的价格
      return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
   }

}

异步任务解决(优化查询案例)

public class CompletableFutureMall1 {
   static List<NetMall1> list= Arrays.asList(
           new NetMall1("JD"),
           new NetMall1("TB"),
           new NetMall1("PDD")
   );
   public static  List<String> getPrice(List<NetMall1> list, String productName){
       return list.stream().map((netMall)-> CompletableFuture.supplyAsync(()->{
          try {
              return String.format(productName+"in %s price is %.2f",netMall.getNetMallName(),netMall.calcPrice(productName));
          } catch (InterruptedException e) {
             throw new RuntimeException(e);
          }
       })).collect(Collectors.toList()).stream().map(s->s.join()).collect(Collectors.toList());
   }
   public static void main(String[] args) {
      long startTime=System.currentTimeMillis();
      List<String> mysql = getPrice(list, "mysql");
      for (String s : mysql) {
         System.out.println(s);
      }
      long endTime=System.currentTimeMillis();
      System.out.println("查询花费时间:"+(endTime-startTime));

   }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class NetMall1 {
   private String netMallName;

   public  double calcPrice(String productName) throws InterruptedException {
      TimeUnit.SECONDS.sleep(1);
      //模拟书的价格
      return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
   }

}

CompletableFuture 常用方法

获得结果和触发计算

_// System.out.println(completableFuture.get()); // System.out.println(completableFuture.get(2L,TimeUnit.SECONDS)); // System.out.println(completableFuture.join()); //如果取的时候,已经计算完成,就正常返回,未计算完成,获得一个备胎xxxx _System.out.println(completableFuture.getNow("xxxx"));

对计算结果进行处理

thenApply 计算结果存在依赖关系,这两个线程串行化 由于存在依赖关系,前一步失败 会影响 后面 程序的运行

   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
         try {
            TimeUnit.SECONDS.sleep(3);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return "ok";
      }).thenApply(f->{
         System.out.println(f+"22222");
         return "2";
      }).thenApply(e->{
         System.out.println(e+"333333");
         return "3";
      });
      System.out.println(completableFuture.join());

   }

handle 计算结果存在依赖关系,这两个线程串行化 前一步失败 会影响 后面 程序的运行

   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
         try {
            TimeUnit.SECONDS.sleep(3);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return "ok";
      }).handle((f,e)->{
         if(e==null){
            System.out.println(f+"22222");
            return "2";
         }
         return null;
      }).whenComplete((v,e)->{
         System.out.println(v+"333333");
      });
      System.out.println(completableFuture.join());

   }

对计算结果进行消费

thenAccept 接受任务的处理结果,并且消费结果,无返回值

   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      CompletableFuture.supplyAsync(()->{
         return "ok";
      }).thenApply(f->{
         return f+2;
      }).thenAccept(r ->{
         System.out.println(r);
      });

   }

thenRun: 任务A执行完执行B,并且B不需要A的结果 thenApply:任务A执行完执行B,B需要A的结果,同时任务B也有返回值 thenAccept:任务A执行完执行B,B需要A的结果,但是任务B没有有返回值

      System.out.println(CompletableFuture.supplyAsync(() -> "resultA")
              .thenRun(() -> System.out.println("111")).join());
      System.out.println(CompletableFuture.supplyAsync(() -> "resultA")
              .thenAccept(r -> System.out.println(r)).join());
      System.out.println(CompletableFuture.supplyAsync(() -> "resultA")
              .thenApply(r -> r+"resultB").join());

111
null
resultA
null
resultAresultB

对计算速度进行选用

public class CompletableFutureFastDemo {
   public static void main(String[] args) {
      CompletableFuture<String> playA=CompletableFuture.supplyAsync(()->{
         System.out.println("A come in");
         try {
            TimeUnit.SECONDS.sleep(2);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return "playA";
      });
      CompletableFuture<String> playB=CompletableFuture.supplyAsync(()->{
         System.out.println("B come in");
         try {
            TimeUnit.SECONDS.sleep(3);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return "playB";
      });
      CompletableFuture<String> result=playA.applyToEither(playB,f->{
         return f;
      });
      System.out.println(result.join());
   }
}
//输出
A come in
B come in
playA

对计算结果进行合并

public class CompletableFutureCombineDemo {
   public static void main(String[] args) {
      CompletableFuture<Integer> integerCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
         System.out.println(Thread.currentThread().getName() + "---启动");
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return 1;
      });
      CompletableFuture<Integer> integerCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
         System.out.println(Thread.currentThread().getName() + "---启动");
         try {
            TimeUnit.SECONDS.sleep(2);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
         return 2;
      });
      CompletableFuture<Integer> res = integerCompletableFuture1.thenCombine(integerCompletableFuture2, (x, y) -> {
         System.out.println("合并两个结果");
         return x + y;
      });
      System.out.println(res.join());
   }
}

CompletableFuture 默认线程池

   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      ExecutorService executorService = Executors.newFixedThreadPool(3);
      CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
         System.out.println("1号任务" + Thread.currentThread().getName());
         return "abcd";
      }).thenRun(() -> {
         System.out.println("2号任务" + Thread.currentThread().getName());
      }).thenRun(() -> {
         System.out.println("3号任务" + Thread.currentThread().getName());
      });
      System.out.println(completableFuture.get());
   }
1号任务ForkJoinPool.commonPool-worker-1
2号任务ForkJoinPool.commonPool-worker-1
3号任务ForkJoinPool.commonPool-worker-1
null


   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      ExecutorService executorService = Executors.newFixedThreadPool(3);
      CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
         System.out.println("1号任务" + Thread.currentThread().getName());
         return "abcd";
      },executorService).thenRun(() -> {
         System.out.println("2号任务" + Thread.currentThread().getName());
      }).thenRun(() -> {
         System.out.println("3号任务" + Thread.currentThread().getName());
      });
      System.out.println(completableFuture.get());
   }

1号任务pool-1-thread-1
2号任务pool-1-thread-1
3号任务pool-1-thread-1
null
   public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
      ExecutorService executorService = Executors.newFixedThreadPool(3);
      CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
         System.out.println("1号任务" + Thread.currentThread().getName());
         return "abcd";
      },executorService).thenRunAsync(() -> {
         System.out.println("2号任务" + Thread.currentThread().getName());
      }).thenRunAsync(() -> {
         System.out.println("3号任务" + Thread.currentThread().getName());
      });
      System.out.println(completableFuture.get());
   }
1号任务pool-1-thread-1
2号任务ForkJoinPool.commonPool-worker-1
3号任务ForkJoinPool.commonPool-worker-1
null

结论: 1没有传入自定义线程池,都用默认线程池ForkJoinPool:: 2传入了一个自定义线程池, 如果你执行第一个任务的时候,传入了一个自定义线程池: 调用thenRun方法构行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池 3备注 有可能处理太快,系统优化切换原则,直接使用main线程处理 其它如:thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它们之间的区别也是同理