JUC之Future、CompletableFuture

        在聊这个工具类之前,先了解下一些概念。

什么是进程

        可以把它简单理解为 系统中运行的一个应用程序,每个进程都有它自己的内存空间和 系统资源。

什么是线程

        同一个进程会有 1个多个线程,是大多数操作系统进行时序调度的基本单元,也被称为“轻量级进程”。(它是CPU调度的最小单位)

        用户线程

                它会完成这个程序需要完成的业务操作。如:main方法 (不做特别说明配置,new出来的线程都是用户线程。)

        守护线程

                它是一种特殊的线程为其它线程服务的,在后台默默地完成一些系统性的工作,如:GC垃圾守护线程。

示例:

public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t开始运行" + (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
            while(true){

            }
        }, "t1");

        // 将t1线程设置为守护线程
        // t1.setDaemon(true);
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "---- end main线程结束" );
    }

代码讲解:

        t1线程作为 用户线程时,执行下列代码是无法结束的,因为while(true)进行了轮训。

        t1线程setDaemon 守护线程时,main线程执行结束后,会立即结束,因为没有 用户线程了

Future

        是什么

                Future接口定义了操作 异步任务执行的方案,如:获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务执行是否完等等。

                如:A(主线程)做饭时发现油没有了,它让B(子线程)去超市帮忙买油,A则继续做饭,这样A和B就可以同时进行,谁也不耽误谁。

        

        有什么用

                它提供了一种 异步并行计算的功能。(它是Java5新增的接口)

                如:主线程需要执行一个很耗时的计算任务,可以通过future把这个任务放到异步线程中执行,主线程则继续处理其他任务或先行结束,再通过Future获取计算结果。

                像项目中下单业务,订单时需要创建订单、需要查询商品库存等等一些不同信息,如果串行化去执行程序执行时间将变成1+1的模式,并行则只需要考虑操作时间最长的那个操作。

FutrueTask

        是什么

                它是Future接口的一种实现。

        有什么用

                它同时满足 多线程有(结果)返回,异步线程特性(能取消、能暂停)三个条件

下面用几个案例讲解下基本使用

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

示例:

public static void main(String[] args)  {
        // 3个任务,目前只有一个main线程执行
//        m1();

        // 3个任务,开启多个线程异步执行
//        m2();

        // 3个任务,使用池化技术异步执行
        m3();

    }

    private static void m3()  {
        // 3个任务,使用池化技术异步执行
        long startTime = System.currentTimeMillis();

        ExecutorService pool = Executors.newFixedThreadPool(3);
        FutureTask<String> ft1 = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "ft1 over";
        });
        pool.submit(ft1);

        FutureTask<String> ft2 = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "ft2 over";
        });
        pool.submit(ft2);

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        try {
            ft1.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        try {
            ft2.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("m3执行时长:" + (endTime - startTime) + "毫秒");
        System.out.println(Thread.currentThread().getName() + "--end");
        pool.shutdown();
    }

    private static void m2() {
        // 3个任务,开启3个线程使用Future接口异步执行
        long startTime = System.currentTimeMillis();
        FutureTask<String> ft1 = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "ft1 over";
        });

        FutureTask<String> ft2 = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "ft2 over";
        });



        Thread t1 = new Thread(ft1, "t1");
        t1.start();
        Thread t2 = new Thread(ft2, "t2");
        t2.start();

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("m2执行时长:" + (endTime - startTime) + "毫秒");
        System.out.println(Thread.currentThread().getName() + "--end");
    }

    private static void m1(){
        // 3个任务,目前只有一个main线程执行
        long startTime = System.currentTimeMillis();
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("m1执行时长:" + (endTime - startTime)+ "毫秒");
        System.out.println(Thread.currentThread().getName() + "--end");
    }

示例简介:

        m1方法,只有一个main线程执行,需要休眠1000毫秒后,才会执行完。

        m2方法,new了多个线程 异步 执行,只有main方法休眠完,执行完即可。

        m3方法,使用池化技术,异步执行 并get(获取结果)结果,此时需要等待执行时间最长的那个异步线程执行完,main线程才能执行结束。

        这种多线程并行执行方法,执行时长是执行时间最长的那个线程的时间,这样比单线程的效率快。

2、阻塞
public static void main(String[] args)  {

        FutureTask<String> ft = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("ft come in");
            return "ft end";
        });

        Thread t1 = new Thread(ft);
        t1.start();

        // 慢任务的get方法,最好放到最后,这样不会影响main线程执行
//        try {
//            System.out.println(ft.get());
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        } catch (ExecutionException e) {
//            throw new RuntimeException(e);
//        }


        System.out.println(Thread.currentThread().getName() + "-- 忙其他任务!");

//        try {
//            System.out.println(ft.get());
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        } catch (ExecutionException e) {
//            throw new RuntimeException(e);
//        }

        // 可以设置FutureTask超时时间,超过多少秒没返回结果,丢异常。超过3秒没返回结果,丢出异常
        try {
            try {
                System.out.println(ft.get(3, TimeUnit.SECONDS));
            } catch (TimeoutException e) {
                throw new RuntimeException(e);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

示例描述:

        FutureTask的get方法会造成阻塞,调取了get方法,会一直等到future返回结果,才会正常执行后面的代码,造成了代码的阻塞。

        ​ 建议:慢任务的get方法,最好放到最后

3、isDone()轮询

public static void main(String[] args) throws Exception {

        FutureTask<String> ft = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("ft come in");
            return "ft end";
        });

        Thread t1 = new Thread(ft);
        t1.start();

        System.out.println(Thread.currentThread().getName() + "-- 忙其他任务!");

        // 使用轮训方式获取结果
        while(true) {
            if (ft.isDone()) {
                System.out.println(ft.get());
                break;
            } else {
                TimeUnit.MILLISECONDS.sleep(500);
                System.out.println("任务未执行结束,请稍等!");
            }
        }
    }

示例描述:

        isDone()方法,判断线程是否执行结束。

        使用isDone方法判断线程状态,可以避免阻塞,但是轮询会消耗CPU资源,导致性能下降。

从上述案例中可以得出结论:

        FutureTask对于结果的获取不是很友好,只能通过阻塞轮询的方式得到任务的结果

CompletableFuture

        为什么会出现CompletableFuture、有什么用

                Future的get方法在得到结果前,会一直阻塞,isDone方法轮询会浪费CPU性能,为了避免这两种情况JDK8新增CompletableFuture,它提供了一种观察者模式类似的机制,可以让任务执行完后通知监听的一方

                CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。

​                 CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另一个阶段。

                 如:CompletableFuture可以通过方法获取到几个异步线程中执行最快的那个线程。也可以进行顺序执行(A线程执行完,再执行B线程),这两种场景换成Future来实现的话就会很麻烦,比如那个顺序执行场景,可以在线程中嵌套调用其他线程 或 通过join方法来等待。

                

它有以下几种实例化方式:

        Completable.runAsync(Runnable runnable)

        Completable.runAsync(Runnable runnable, Executor executor)

        以上两个方法,用来创建CompletableFuture类,没有返回值

示例:

public static void main(String[] args) throws Exception {

        CompletableFuture cf = CompletableFuture.runAsync(() -> {
            // 不定义线程池,默认使用forkJoinPool,使用该线程池,它是一个守护线程,main线程执行完毕,它会立即停止。
            System.out.println(Thread.currentThread().getName() + "come in");

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

        System.out.println("main 线程处理其他问题");
        // runAsync 不返回结果,打印null
        System.out.println(cf.get());

        System.out.println("\n自定义线程池runAsync");
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture cf2 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "come in");

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, executorService);

        System.out.println("main 线程处理其他问题");
        // runAsync 不返回结果,打印null
        System.out.println(cf2.get());
        // runAsync自定义线程池,需要手动关闭线程池,否则会阻塞
        executorService.shutdown();
    }

        Completable.supplyAsync(Supplier<U> supplier)

        Completable.supplyAsync(Supplier<U> supplier, Executor executor)

        以上两个方法,用来创建CompletableFuture类,有返回值

示例:

        supplyAsync方法,执行结束会回调whenComplete方法,可以获取线程计算的结果。

        出现异常会调用exceptionally方法,可以捕获线程执行的异常。

public static void main(String[] args) throws Exception {

//        CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
//            // 不定义线程池,默认使用forkJoinPool
//            System.out.println(Thread.currentThread().getName() + "come in");
//
//            try {
//                TimeUnit.SECONDS.sleep(1);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            return "spully forkjoin pool";
//        });
//
//        System.out.println("main 线程处理其他问题");
//        // supplyAsync 不返回结果,打印spully forkjoin pool
//        System.out.println(cf.get());
//
//        System.out.println("\n自定义线程池supplyAsync");
//        ExecutorService executorService = Executors.newFixedThreadPool(3);
//        CompletableFuture cf2 = CompletableFuture.supplyAsync(() -> {
//            System.out.println(Thread.currentThread().getName() + "come in");
//            try {
//                TimeUnit.SECONDS.sleep(1);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            return "spully custom pool";
//        }, executorService);
//
//                System.out.println(Thread.currentThread().getName() + "线程处理其他问题");
//        // supplyAsync 返回结果,spully custom pool
//        System.out.println(cf2.get());
//        // supplyAsync,需要手动关闭线程池,否则会阻塞
//        executorService.shutdown();

        // supplyAsync 通过回调方法拿到结果,并通过回调方法抛出异常
        supplyAsyncTest();
    }

    private static void supplyAsyncTest(){
        AtomicInteger a = new AtomicInteger();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture cf2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "come in");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            int num = ThreadLocalRandom.current().nextInt(10);
            if (num > 2) {
                int i = 10 / 0;
            }
            return num;
        }, executorService).whenComplete((v, e) -> {
            if (Objects.isNull(e)) {
                System.out.println("计算结果:"+ v);
                a.set(v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            return null;
        });

        System.out.println(Thread.currentThread().getName() + "线程处理其他问题");
        System.out.println("计算结果:" + a);
        executorService.shutdown();
    }

注意:

        1、以上方法中的Executor executor参数,没有指定线程池,默认使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。          

        2、CompletableFuture 不建议使用new方法来创建其对象示例。

CompletableFuture优点

        1、异步任务结束时,会自动回调某个对象的方法(whenComplete方法)。

        2、主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。

        3、异步任务出错时,会自动回调某个对象的方法(exceptionally方法)。

异步编排中常用的方法简介:

1、获得结果和触发计算

        get、join、getNow、complete方法(主动触发计算)。

        以上方法用来获取CompletableFuture的结果。

示例:

public static void main(String[] args) throws Exception {
        // 获得结果和触发计算
        m1();
    }


    /**
     * 获得结果和触发计算
     * get、join、getNow、complete
     */
    private static void m1() throws Exception {
        CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
            System.out.println("supplyAsync come in");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "abc";
        });

        // get会造成阻塞
//        System.out.println(cf.get());
        // supplyAsync执行超过2秒,就丢出异常,不超过2秒执行完毕,则正常返回
//        System.out.println(cf.get(2, TimeUnit.SECONDS));
        
        // join方法在编译时不会出现检查型异常,get方法会出现这个异常
//        System.out.println(cf.join());
        
        // getNow(T IfAbsent)方法,如果线程已经执行完成,则返回结果值(或抛出任何遇到的异常),否则返回给定的值IfAbsent。此方法在执行时不会改变线程状态
//        System.out.println(cf.getNow("getNow result null"));

        /**
         * complete(T value)返回 如果不是已经完成,将返回的值 get()种相关方法为给定值。
         * 结果 true如果此调用导致此CompletableFuture过渡到完成状态,否则 false
         * complete方法执行会改变线程状态,true已完成,false是未完成
         */
        TimeUnit.SECONDS.sleep(1);
        System.out.println(cf.complete("complete result boolean") + "\t" + cf.join());
    }

2、对计算结果进行处理

        thenApply、handle方法。

        thenApply handle方法,正常执行完都可以得到结果,且可以对结果进行加工,如:转换、过滤、计算等操作。

        发生异常thenApply会立即停止执行后面thenApply方法,而直接执行exceptionally方法捕获异常。

        发生异常handle会执行完所有的handle方法,再执行exceptionally方法捕获异常。

        说明

        whenComplete方法,无论前序任务成功还是失败,都会执行,可以查看结果或异常,但不会改变结果,它不会对异常进行处理,会将异常传递给后续操作。

        exceptionally方法,会捕获异常捕获后可以对异常进行处理,类似try--catch。

示例:

public static void main(String[] args) throws Exception {
        // 对计算结果进行处理
        m2();
    }


    /**
     * 对计算结果进行处理
     * thenApply、handle
     */
    private static void m2(){
        // thenApply,将线程串行化自行
        // 注意:CompletableFuture不定义线程池,默认使用forkjoinpool线程池,它是一个守护线程,main(用户线程)主线程执行完,它会结束
        ExecutorService pool = Executors.newFixedThreadPool(3);
//        CompletableFuture.supplyAsync(() -> {
//            System.out.println("supplyAsync come in");
//            return 1;
//        }, pool).thenApply(f -> {
//            System.out.println("thenApply 222");
//            return f + 1;
//        }).thenApply(f -> {
//            // 注意: thenApply方法,在遇到异常后,不会进入其它的thenApply中,会执行exceptionally方法将捕获异常
//            int i = 10/0;
//            System.out.println("thenApply 333");
//            return f + 2;
//        }).thenApply(f -> {
//            System.out.println("thenApply 555");
//            return f + 5;
//        }).whenComplete((v, e) -> {
//            System.out.println("计算结果:" + v);
//        }).exceptionally(e -> {
//            e.printStackTrace();
//            System.out.println(e.getCause() + ", msg : " + e.getMessage());
//            return null;
//        });
//
//        System.out.println(Thread.currentThread().getName() + "去做其他事情!");


        CompletableFuture.supplyAsync(() -> {
            System.out.println("supplyAsync come in");
            // 注意: handle,在遇到异常后,会把其他的handle方法执行完(无法得到结果),才会执行exceptionally方法将捕获异常
            int i = 10/0;
            return 1;
        }, pool).handle((f,e) -> {
            System.out.println("handle 222 ->" + f);
            return f + 1;
        }).handle((f,e) -> {
            System.out.println("handle 333 ->" + f);
            return f + 2;
        }).handle((f,e) -> {
            System.out.println("handle 555 ->" + f);
            return f + 5;
        }).whenComplete((v, e) -> {
            System.out.println("计算结果:" + v);
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println(e.getCause() + ", msg : " + e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName() + "去做其他事情!");

        // 注意:CompletableFuture自定义线程池,需要关闭线程池,否则会一直阻塞
        pool.shutdown();
    }

3、对结算结果进行消费

        thenAccept方法。

        thenRun,不依赖上个任务结果,无返回。

        thenApply,依赖上个任务结果,且有返回。

        thenAccept,依赖上个任务结果,无返回。(一般作为任务终点,用来记录日志)

入参、返回值对比

方法输入参数返回值处理异常?修改结果?典型场景
thenApply前序结果 (T)U数据转换
thenAccept前序结果 (T)void消费结果(如保存数据)
thenRunvoid执行与结果无关的收尾操作

异常对比

方法是否捕获异常?是否传递异常?能否恢复异常?
thenApply
thenAccept
thenRun
handle❌(可覆盖)

示例:

public static void main(String[] args) throws Exception {
        // 对结算结果进行消费
        m3();
    }

	/**
     * 对结算结果进行消费
     * thenAccept
     */
    private static void m3(){
        // 任务A执行完执行任务B,任务B不依赖任务A的结果,无返回值
        // thenRun是个 多线程函数
        System.out.println("thenRun(Runnable runnable) -----");
        System.out.println(
            CompletableFuture.supplyAsync(() -> {
                return "resultA";
            }).thenRun(() -> {
                System.out.println("thenRun print");
            }).join()
        );

        // 任务A执行完执行任务B,任务B依赖任务A的结果,无返回值
        // thenAccept是个 消费型函数
        System.out.println("\n thenAccept(Consumer action) -----");
        System.out.println(
            CompletableFuture.supplyAsync(() -> {
                return "resultA";
            }).thenAccept(f -> {
                System.out.println("thenAccept param f = " + f);
            }).join()
        );

        // 任务A执行完执行任务B,任务B依赖任务A的结果,有返回值
        // thenApply是个 功能型函数
        System.out.println("\n thenApply(Function fu) -----");
        System.out.println(
            CompletableFuture.supplyAsync(() -> {
                return "resultA";
            }).thenApply(f -> {
                System.out.println("thenApply param f = " + f);
                return f + "resultB";
            }).whenComplete((v, e) -> {
                System.out.println("计算结果:" + v);
            }).exceptionally(e -> {
                e.printStackTrace();
                return null;
            }).join()
        );
        System.out.println(Thread.currentThread().getName() + "执行其他任务");
    }

4、对计算速度进行选用

        applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)

示例:

        **能不能同时比对多个线程的执行速度?**

public static void main(String[] args) throws Exception {
        // 对计算速度进行选用
        m4();
    }

/**
     * 对计算速度进行选用
     * applyToEither
     */
    private static void m4(){
        CompletableFuture<String> t1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "thread 1";
        });

        CompletableFuture<String> t2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "thread 2";
        });

        // applyToEither对比两个线程任务执行时长,哪个线程执行的快,返回哪个线程的结果
        // f参数获取最先执行完线程的结果
        CompletableFuture<String> t3 = t1.applyToEither(t2, f -> {
            return f + " fast";
        });

        System.out.println(Thread.currentThread().getName() + "处理其他任务");
        System.out.println(t3.join());
    }

5、对计算结果进行合并

        thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)

示例:

public static void main(String[] args) throws Exception {
        // 对计算结果进行合并
        m5();
    }

/**
     * 对计算结果进行合并
     * thenCombine
     */
    private static void m5(){
        CompletableFuture<Integer> t1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            return 10;
        });

        CompletableFuture<Integer> t2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            return 20;
        });

        // thenCombine方法将两个异步线程结果合并到一个异步线程中
        // a、b参数 分别是被合并的两个线程的 结果
        CompletableFuture<Integer> t3 = t1.thenCombine(t2, (a, b) -> {
            return a + b;
        });

        System.out.println(Thread.currentThread().getName() + "处理其他任务");
        System.out.println(t3.join());
    }

        通过上述方法,对CompletableFuture异步并行工具类有了一定的理解,可以通过get(会阻塞)、join等方法获取结果,whenComplete方法处理捕获并处理异常,exceptionally方法观察执行结果。

        在实际项目中,需要根据实际业务场景来灵活选取使用的方法,在自己实际项目中主要是用来对大业务中可以并行查询的数据操作进行优化处理,也用来异步对业务场景的日志进行记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值