2.1 CompletableFuture详解

CompletableFuturejava架构位置

CompletableFuture实现了Future接口和 CompletionStage接口 架构图如下

在这里插入图片描述

CompletionStage是什么

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

一句话总结:餐馆点菜

详情:我现在点的一个菜在餐馆中没有材料,老板同时要求3个人把所有材料全部都买回来了,然后厨师下锅把这些材料依次添加进去,最后出锅的是一道已经做好的菜品 而不是材料

一个阶段的计算或执行可以使Function,Consumer或者Runnable。

比如stage.thenApply( x -> square(x) ). thenAccept( x -> System.out.print(x) ). thenRun( () -> System.out.print() )

一个阶段的执行可能是被单个阶段的完成触发,也可能是有多个阶段一起触发

CompletableFuture是什么

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

CompletableFuture可能代表一个明确的Future,也有可能代表一个完成阶段 ( CompletionStage ), 他支持在计算完成以后出发一些函数或执行某些动作

CompletableFuture实现了 Future接口就相当于是具有了FutureTask的特性,然后在这个基础上有添加了一个新的特性:CompletionStage接口

CompletableFuture的4个核心静态方法 创建一个异步操作

4个静态方法中,不用执行 future.get() 方法 程序会自动创建一个线程运行 sync内部的内容,如果调用了 future.get() 方法 使用的是 FutureTask的特性,会将线程阻塞

如果没有指定线程池,直接使用默认的ForkJoinPool.commonPool() 作为线程池执行异步代码

如果指定了线程池,就使用自定义的线程池执行异步代码

无返回值 runAsync()

public static CompletableFuture runAsync(Runnable runnable)

使用默认的线程池异步开启一个线程进行调用

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
  	System.out.println(Thread.currentThread().getName() + "异步操作");
});
System.out.println("main线程执行");

在这里插入图片描述

public static CompletableFuture runAsync(Runnable runnable, Executor executor)

使用自己创建的线程池开启一个线程进行调用

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        1,
        5,
        10,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
);

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "异步操作");
}, threadPoolExecutor);
System.out.println("main线程执行");

threadPoolExecutor.shutdown();

在这里插入图片描述

有返回值 supplyAsync()

public static CompletableFuture supplyAsync(Supplier supplier)
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "");
    return 1;
});
System.out.println(supplyAsync.get());
System.out.println("main线程运行");

在这里插入图片描述

public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        1,
        5,
        10,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
);

CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "");
    return 2;
}, threadPoolExecutor);
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
threadPoolExecutor.shutdown();
在这里插入图片描述

证明:CompletableFuture是一个和主线程无关的异步任务

注意:和主线程无关的前提是 CompletableFuture 没有调用 get,join,complete方法

功能:

  1. 创建一个一步线程,异步线程首先返回1024,然后进入 whenComplete方法,

  2. supplyAsync中如果出错 那么打印出 e,v为null,

  3. 如果supplyAsync没有出错那么打印出v,e为null

  4. 如果以上步骤有出错的地方,那么直接进入 exceptionally,打印出异常信息并返回1

public class CompletableFutureTest {
    public static void main(String[] args) {
        //开启一个异步线程, 主线程执行完毕, 如果异步线程没有执行完也会被关闭
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            return 1024;
        }).whenComplete((v,e) ->{
            System.out.println(v);
            System.out.println(e);
        }).exceptionally(e ->{
            e.getMessage();
            System.out.println("出现异常");
            return 1;
        });
      
        System.out.println("main 线程执行......");
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

证明和主线程无关:注意了 这里并没有调用 get() , join, complete 方法

如果主线程中 没有让主线程sleep 2s 结果如下

在这里插入图片描述

如果主线程中 让主线程sleep 2s 结果如下

在这里插入图片描述

如果调用了 get,或者 join方法,则会进入等待状态

案例:模拟一个比价功能

功能:现在有 淘宝,京东,拼夕夕,当当等多个网站售卖mysql 这本书,我现在要比较这本书在每个网站的价格是多少

package com.example.completablefuture;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * @create 2021-03-09 5:49 下午
 *
 * 比价案例
 */
public class CompletableFutureFunctionTest {

    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("pdd"),
//            new NetMall("tb"),
//            new NetMall("tc"),
            new NetMall("td")
    );

    /**
     * 方式1,一个网站一个网站的得到商品的价格
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceByStep(List<NetMall> list, String productName){
        // %.2f 表示保留2位小数
        return list.stream()
                .map(f -> String.format(productName + " in %s price is %.2f", f.getMallName(), f.calcPrice(f.getMallName())))
                .collect(Collectors.toList());
    }

    /**
     * 方式2, 使用CompletableFuture 并发处理
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceByStepSync(List<NetMall> list, String productName){
        // 处理思路: 先将 List<NetMall> 转换成 -> List<CompletableFuture<String>>的list 然后对这个 list进行 join操作
        return list.stream()
                .map(f -> CompletableFuture.supplyAsync(() ->
                        String.format(productName + " in %s price is %.2f", f.getMallName(), f.calcPrice(f.getMallName()))))
                .collect(Collectors.toList())
                .stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        long beforeSearchTime =  System.currentTimeMillis();
        List<String> lists = getPriceByStep(list, "mysql");
        for (String element : lists) {
            System.out.println(element);
        }
        long afterSearchTime = System.currentTimeMillis();
        System.out.println(afterSearchTime - beforeSearchTime);

        long beforeSearchTime1 =  System.currentTimeMillis();
        List<String> lists1 = getPriceByStepSync(list, "mysql");
        for (String element : lists1) {
            System.out.println(element);
        }
        long afterSearchTime1 = System.currentTimeMillis();
        System.out.println(afterSearchTime1 - beforeSearchTime1);
    }
}

@Getter
@AllArgsConstructor
class NetMall{
    private String mallName;

    public double calcPrice(String productName){
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        //ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0); 表示随机生成一个浮点类型的数据
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

结果如图所示:每添加一个新的网站,就会让一个网站一个网站的比较的那种方式变慢,而使用 CompletableFuture 的方式 几乎都是1s左右就执行完成

在这里插入图片描述

注意:使用CompletableFuture 并发处理 方法解读:

提问:有的人可能会觉得 第5、6这 2行 ( 红框框起来的2行 ) 可以删掉,没有什么实际作用,因为加了这2行是转换成 Stream<CompletableFuture>,不加这2行也是转换成 Stream<CompletableFuture>,那么为什么要加呢

解答:因为我需要操作的是 List<CompletableFuture> 而不是 Stream<CompletableFuture>,使用红框框起来的2句应该分开理解,如果不执行红框框起来的内容 虽然结果上是 Stream<CompletableFuture> 但实际上是缺了点东西的, 第二次调用 stream 的目的是 将了List<CompletableFuture>的list 转换成 stream,然后通过这个 stream执行 join操作

在这里插入图片描述

CompletableFuture中的常用方法

1. 获得结果和触发计算

1.1. get()、join()、get(long timeout, TimeUnit unit)

get():等待子线程完成任务,获得返回结果, 不管子任务要执行多久,都在外面等着 – FutureTask特性 一句话总结:不见不散

join():和get() 方法一样,唯一的区别就是不抛出异常

public class GetResult {
    public static void main(String[] args) {
      
        CompletableFuture future = CompletableFuture.supplyAsync(() ->{
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        });

        try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
      	System.out.println(future.join());
    }
}

在这里插入图片描述

get(long timeout, TimeUnit unit):等待 timeout 时间,如果该时间内子线程任务完成,则和get() 一样,如果该时间内子任务没有完成,则抛出 TimeOutException, 一句话总结:过时不够

public class GetResult {
    public static void main(String[] args) {

        CompletableFuture future = CompletableFuture.supplyAsync(() ->{
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        });

        try { System.out.println(future.get(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }
    }
}

在这里插入图片描述

1.2. getNow(T valueIfAbsent)

当执行到getNow时,如果程序执行完成,则取执行完成的那个结果,如果程序没有执行完成,则返回 getNow() 方法中传入的参数,

valueIfAbsent相当于是一个default

public class GetResult {
    public static void main(String[] args) {
        CompletableFuture future = CompletableFuture.supplyAsync(() ->{
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        });

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(future.getNow(2));
    }
}

子线程已经计算完结果返回:

在这里插入图片描述

子线程没有计算完结果返回:

在这里插入图片描述

1.3. complete(T value)

complete(T value):当程序执行complete方法时,首先判断 completableFuture中的内容是否执行完了,如果执行完了,说明打断失败,返回completableFuture返回的内容,如果completableFuture中的内容没有执行完,那么就打断completableFuture中的内容,然后返回complete(T value)中的内容

complete(T value)常配合 get(), join()方法一起使用

public class GetResult {
    public static void main(String[] args) {
        CompletableFuture future = CompletableFuture.supplyAsync(() ->{
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        });

        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
//        System.out.println(future.getNow(2));
        System.out.println(future.complete(2) + "," + future.join());
    }
}

打断失败:

在这里插入图片描述

打断成功:

在这里插入图片描述

2. 对计算结果进行处理

2.1. thenApply方法

thenApply的计算结果存在依赖关系,线程串行化,由于依赖关系(当前不认错,不走下一步),当前步骤有异常的话就叫停

在这里插入图片描述

public class handleCalculation {
    public static void main(String[] args) {
			CompletableFuture future = CompletableFuture.supplyAsync(() -> {
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        }).thenApply(v1 -> {
            System.out.println("----1");
//            int i = 1 / 0;
            return v1 + 1;
        }).thenApply(v2 -> {
            System.out.println("----2");
            return v2 + 1;
        }).whenComplete( (v, e) ->{
            if(e == null){
                System.out.println("---result" + v);
            }
        }).exceptionally(e ->{
            e.printStackTrace();
            return null;
        });
        System.out.println("future:" + future.join());
    }
}

在这里插入图片描述

如果没有异常正常执行结果输出:

在这里插入图片描述

如果抛出异常:

thenApply后续部分就不走了,直接进入exceptionally中

在这里插入图片描述

2.2 handle

有异常也可以往下走,根据带的异常参数可以进一步处理

public class handleCalculation {

    public static void main(String[] args) {
        CompletableFuture future = CompletableFuture.supplyAsync(() -> {
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("进入异步方法");
            return 1;
        }).handle((v1, e) -> {
            System.out.println("----1");
            int i = 1 / 0;
            return v1 + 1;
        }).handle((v2, e) -> {
            System.out.println("----2");
            return v2 + 1;
        }).whenComplete( (v, e) ->{
            if(e == null){
                System.out.println("---result" + v);
            }
        }).exceptionally(e ->{
            e.printStackTrace();
            return null;
        });
        System.out.println("future:" + future.join());
    }
}

如果出现异常 程序还是可以继续往后执行

在这里插入图片描述

2.3. whenComplete 、 exceptionally方法

在这里插入图片描述

当上一步操作完成了,判断上一步操作过程中是否出错,将上一步的操作结果、可能会出现的异常作为参数传入,

如果上一步正常执行,则第一个参数就是上一步执行的结果,异常信息就会显示为null

如果上一步异常执行,则第一个参数 为null, 异常信息 是这个异常信息, get() 的结果为 exceptionally() 方法中 return 的信息

举个例子:

        //新开一个子线程干活, 有返回值
        Integer integer = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":completableFuture2");
//           int i = 10 / 0;
            return 1024;
        }).whenComplete((t, u) -> {
            //如果正常执行,就会吧t打印出来
            System.out.println(" --- t= " + t);
            System.out.println(" --- u= " + u);
        }).exceptionally(f -> {
            //只有当程序出错的时候,才会进入这个方法,进入这个方法后, 也会让这个方法有一个异常信息
            System.out.println("--exception:" + f.getMessage());
            return 444;
        }).get();
        System.out.println(integer);

正常执行结果:

在这里插入图片描述

异常执行结果:

在这里插入图片描述

2.4. 总结thenApply()、handle()、whenComplete、exceptionally

exceptionally 相当于是try - catch

whenComplete + handle 相当于是 try - finally

2.5. 以上所有方法都存在一个带Async后缀的方法,2着的区别

拿thenApply举例:执行当前任务的线程继续执行 thenApply的任务:我最开始从线程池中拿了一个线程执行 supplyAsync方法后 依旧是拿这个线程执行thenApply方法

thenApplyAsync:执行吧 thenApplyAsync 这个任务继续交给线程池中其他线程来执行。我最开始从线程池中拿了一个线程执行 supplyAsync方法后,由线程池重新分配一个线程来执行 thenApplyAsync, 每次用一个 带Async后缀的方法,就会从线程池中拿一个

3.对结果进行消费

3.1 thenAccept

接收任务的处理结果,并消费处理,无返回结果

将上面的执行结果拿过来消费,没有返回内容

CompletableFuture.supplyAsync(() -> {
    return 1 + 1;
}).thenApply(v -> {
    return v + 1;
}).thenAccept(v -> {
    System.out.println(v);
});

结果:

在这里插入图片描述

3.2 thenRun()

任务A执行完执行B,并且B不需要A的结果, 很少用到

public static void main(String[] args) {
    System.out.println(CompletableFuture.supplyAsync(() -> {
        return 1 + 1;
    }).thenApply(v -> {
        return v + 1;
    }).thenRun(() -> {

    }).join());
}

4.对计算速度进行选用

applyToEither:对计算结果进行优先选用

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

public class ComsumeResult {

    public static void main(String[] args) {

        System.out.println(CompletableFuture.supplyAsync(() -> {
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            return 1;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            return 2;
        }), r -> {
            return r + 1;
        }).join());

    }
}

看哪个CompletableFuture 中的内容先执行完,就用先执行完的这个结果进行一系列操作 ,注意 虽然说优先选用执行速度快的那个作为参数,执行速度快的那个做了参数以后并不是说执行速度慢的那个就停了,执行速度慢的那个还是会执行完

在这里插入图片描述

5.对计算结果进行合并

先完成的先等着,等所有任务完成以后,再将结果合并

public static void main(String[] args) {

    System.out.println(CompletableFuture.supplyAsync(() -> {
        return 1;
    }).thenCombine(CompletableFuture.supplyAsync(() -> {
        return 2;
    }), (r1, r2) -> {
        return r1 + r2;
    }).join());
}

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值