同步异步与CompletableFuture简单介绍

本文深入讲解了Java中的同步与异步编程概念,并重点介绍了Future、线程池及CompletableFuture的应用。通过实例演示了CompletableFuture的强大功能,包括流式调用、异常处理、任务组合等。

同步异步与CompletableFuture

同步异步概念

同步方法必须等方法调用完才能返回结果,类似与一条线;异步方法像是开启一个通知,通知后立即返回。

Future

​ 对于异步任务,线程任务中,如果有返回值,如果我们不是很急着需要获取返回结果,那么需要用到Future。当需要用到任务结果的时候,可以调用future.get 获取放回值。

线程池

在这里插入图片描述

备注:上图截取于《java 高并发程序设计》一书,部分方法省略。 教详细的Excutorservice 在jdk11中有如下方法:

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();


    boolean isShutdown();


    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;


    <T> Future<T> submit(Callable<T> task);

    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

​ 我们常用的线程池Excutorservice接口,其中有三个重载submit方法可以返回future。

​ 这里如果需要获取任务的返回值,那么参数传递应该是submit(Callable task),即传递一个具有返回值的任务。

CompletableFuture

​ CompletableFuture是future的一种拓展,具备future的所有特性,它提供了更强大的功能,适合链式编程。

​ 主要提供两类工厂方法:supplyAsync()、 runAsync()。其中supplyAsync用于获取有放回置的场景,runAsync用于获取没有返回值的场景。

​ 另外,这两类方法均支持指定线程池,如果不指定,则默认使用系统的ForkJoinPool线程池。ForkJoinPool会根据系统cpu核数来构建不同的线程池。

待补充点1:ForkJoinPool底层构建不同线程池逻辑。

代码示例 (下列示例均不指定特定线程池)——

supplyAsync()

具备返回值的

    @Test
    public void test1() {
        //具有返回值的
        CompletableFuture<Boolean> booleanCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //业务代码返回
            return true;
        });
    }

集合返回值并且阻塞

    @Test
    public List<CompletableFuture<Boolean>> test11() {
        //集合的返回   异步并且阻塞
        List<CompletableFuture<Boolean>> futureList = list.stream().map(class1 -> CompletableFuture.supplyAsync(() -> {
            //每个对象分别返回业务代码
            return true;
        })).collect(Collectors.toList());
        futureList.forEach(CompletableFuture::join);
        return futureList;
    }

runAsync()

无返回值

    @Test
    public void test2() {
        //无返回值的
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            //业务代码无返回值
        });
    }




    @Test
    public void test22() {
        //无返回值 阻塞
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            //业务代码无返回值
        });

        voidCompletableFuture.join();
        //voidCompletableFuture.get(); get 的需要try catch 捕获异常
    }

————————————————————————————————————————————20210119

流式调用和通知

公用判断方法

public class JudgeIsDouble {
    public static int judgeDouble(int num) {
        if (num%2 == 0) {
            return 1;
        }
        return 0;
    }
}
  • 流式调用之并行

    流式调用,配合并行可增加效率

 @Test
    public void test11() {
        /*IntStream range = IntStream.range(1, 1000000);
        range.mapToObj(JudgeIsDouble::judgeDouble).collect(Collectors.toList());*/

        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        //集合的返回   异步   非并行
        long time1 = System.currentTimeMillis();
        list.stream().map(obj -> CompletableFuture.supplyAsync(() -> {
            //业务代码 每个对象分别返回
            return JudgeIsDouble.judgeDouble(obj);
        }));
        long time2 = System.currentTimeMillis();
        System.out.println(time2-time1);


		//集合的返回   异步   并行
        long time11 = System.currentTimeMillis();
        list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
            //业务代码 每个对象分别返回
            return JudgeIsDouble.judgeDouble(obj);
        }));
        long time22 = System.currentTimeMillis();
        System.out.println(time22-time11);

    }
//重复多次 第二种均比第一种时间少
294
158
  • CompletableFuture 流式调用
    @Test
    public void test4() {
        for (int i = 0; i < 100; i++) {
            list.add(i);
        }

        Stream<CompletableFuture<Void>> completableFutureStream = list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
                    //业务代码 每个对象分别返回
                    return JudgeIsDouble.judgeDouble(obj);
                }).thenApply(i -> Integer.toString(i))
                .thenApply((res) -> "\"" + res + "\"")
                .thenAccept(System.out::println));
        //等待完成 不加这个上面代码式异步的,直接输出了
        completableFutureStream.forEach(CompletableFuture::join);
        
    }

注意,completableFutureStream最后必须阻塞后才能,否则主线程无法获取异步线程计算的返回值。另外,

get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出,而join会抛出未经检查的异常。

  • CompletableFuture 异常

    public class JudgeIsDouble {
        public static int judgeDouble(int num) {
            if (num == 6) {
                throw new IndexOutOfBoundsException("error ! num can not equal 6");
            }
            if (num%2 == 0) {
                return 1;
            }
            return 0;
        }
    }
    

    可以配合流式调用一起,加上exceptionally()方法抛出异常。

    @Test
    public void test5() {
        for (int i = 0; i < 100; i++) {
            list.add(i);
        }

        Stream<CompletableFuture<Integer>> completableFutureStream = list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
            return JudgeIsDouble.judgeDouble(obj);
        }).exceptionally(ex -> {
            System.out.println(ex.toString());
            return 2;
        }));
        //等待完成 不加这个上面代码式异步的,直接输出了
        completableFutureStream.forEach(obj -> {
            try {
                obj.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }


—————————————————————————————————— 结果
java.util.concurrent.CompletionException: java.lang.IndexOutOfBoundsException: error ! num can not equal 6

  • CompletableFuture 完成通知 (待)

  • 多个异步任务处理

    假设有一种场景: 你去餐馆点菜,你去之后一定是按照菜单点餐,点餐后厨师会炒菜,服务员1准备餐具,服务员2准备饮料,服务员3在你们厨师炒菜好了以及前面的服务员准备好餐具和饮品后再上饭,然后你才吃到饭菜。一共四个任务,有并行处理的,有顺序处理的。

    @Test
    public void test6() throws ExecutionException, InterruptedException {

        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            int num = 5;
            System.out.printf("第一步:客户点餐, 点了 %d 个菜", num);
            System.out.println("\n");
            return num;
            //点菜后,这个任务的结果:点菜数目,传递到下一个CompletableFuture,利用thenCompose
        }).thenCompose((num) -> {
            //点菜是阶段1, 厨师做菜 和服务员准备餐具 是阶段2,阶段2的任务没有现后关系
           return CompletableFuture.supplyAsync(() -> {
                System.out.println("第二步:厨师开始做菜!");
                for (int i = 1; i <= num; i++) {
                    System.out.printf("正在准备第%d个菜", i);
                    System.out.print("\n");
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return "厨师准备完成\n";
                //厨师做菜和服务员准备餐具 不相关的两个并行任务, 但是需要两个都准备好才行
            }).thenCombine(CompletableFuture.supplyAsync(() -> {
                System.out.println("第二步:服务员1正在准备餐具!");
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "服务员1准备完成了\n";
            }), (i, j) -> (i + j)).thenCombine(CompletableFuture.supplyAsync(() -> {
                System.out.println("第二步:服务员2正在准备饮品");
               try {
                   sleep(2000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
                return "服务员2准备完成了\n";
            }), (str1, str2) -> "第二步完成!");

        }).thenCompose((str) -> CompletableFuture.supplyAsync(() -> {
            System.out.println(str);
            System.out.println("第三步:菜和餐具准备完成,服务员3开始准备饭!");
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "服务员3盛饭完成,客户准备吃饭";
        })).thenApply((str) -> {
            System.out.println(str);
            String res = "客人开始吃饭";
            return res;
        });
        //阻塞查看结果
        String s = stringCompletableFuture.join();
        //String s = stringCompletableFuture.get();
        System.out.println(s);
    }

​ 结果:

F:\program\jdk-11.0.8+10\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "TestCompletableFuture,test6
第一步:客户点餐, 点了 5 个菜

第二步:厨师开始做菜!
正在准备第1个菜
第二步:服务员1正在准备餐具!
第二步:服务员2正在准备饮品
正在准备第2个菜
正在准备第3个菜
正在准备第4个菜
正在准备第5个菜
第二步完成!
第三步:菜和餐具准备完成,服务员3开始准备饭!
服务员3盛饭完成,客户准备吃饭
客人开始吃饭

Process finished with exit code 0

​ 上面案例中,可以分为这么几个阶段:第一阶段,客户点餐,第二阶段:厨师开始炒菜,服务员1和服务员2准备餐具和饮品;第三阶段:厨师炒好菜了,服务员1和2也准备好东西了,服务员3开始盛饭。服务员3盛饭完成后,最后客人开始吃饭。

需要注意几点

​ thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个completionStage,它们之间存在着业务逻辑上的先后顺序。thenCompose 和thenApply方法的区别是:thenApply还是原来的CompletableFuture,而thenCompose 用以连接新的CompletableFuture,它返回的是一个CompletableFuture对象。

如果某个场景需要用到两个异步任务的结果,可以使用thenCombine,它是用以连接两个任务的一种方法。

方法签名是:

    public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(null, other, fn);
    }

它会完成在调用任务和other异步任务后,将结果一起传递给BiFunction,BiFunction是是一种接受两个参数有一个返回值的接口,执行结束后最终返回的是一个CompletableFuture对象!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值