JUC之CompletableFuture

CompletableFuture

需求

需求介绍

现在,需要实现一个商城的详情页,就如同京东、淘宝一样。在这个页面中,有一些不是行必要的信息和关键信息不是防止一个业务模块中的

如:商品的基本信息是放入shoping数据库,由商品的业务模块进行处理,商品介绍中的信息如介绍图片保存在image数据库中,由图片模块进行处理

需求解决

首先,可以使用基本是 思想对业务进行处理,即按照基本业务进行处理

两个基本的方法代表两个业务:查询商品和查询图片

public static void shopping(){
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("查询到了商品的详细信息");
}
public static void image(){
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("远程调用:查询到了商品图片的信息");
}

其中,远程调用因为网络的问题,所有需要的时间比本地调用的时间要长一些

public static void main(String[] args) {
    
    long startTime = System.currentTimeMillis();
    Main.shopping();
    Main.image();
    long endTime = System.currentTimeMillis();
    System.out.println("花费的时间:"+(endTime-startTime));
}

运行程序,所花费的时间是3s

运行结果:

查询到了商品的详细信息
远程调用:查询到了商品图片的信息
花费的时间:3024

需求 ->> 性能

解决 了基本的需求,现在需要将业务进行从需求到性能的改变。即如何能将这3s的时间进一步的缩短呢?我们可以做如下的思考

  • 两个业务,一个业务是1s,一个业务是2s,这两个业务需求的时间是不变的。
  • image的业务是非必要的,即如果调用偶尔超时,对整个业务来说是没什么影响的

以上两点,我们发现了一种解决的方式:新开一个线程,让这个线程去执行查询图片的业务,可以给这个线程定义一个超时时间,若在超时,则报一个异常。

Future

Future是在jdk1.5 的时候,JUC大神Doug Lea编写的一个用于异步计算和编排的一个接口。Future是一个接口,它由一个本源的实现类FutureTask

private static void m3() throws Exception {
    FutureTask<Integer> futureTask = new FutureTask<>(() -> {
        Main.image();
        return 1;
    });
    long startTime = System.currentTimeMillis();
    // 开另外一个线程去执行查询图片,因为查询顺序执行的,所以如果把这防止shopping后面,
    // 则这个线程会让main停止1s后才去执行,体现不出效果
    new Thread(futureTask,"t1").start();
    Main.shopping();
    // 程序在这个两秒中后不返回结果,则抛出异常
    // 注意:程序在前面就已经开始执行了,所以到这个get()的时候,程序已经执行了1s左右了
    // 所以,虽然这里是2s,但是不会超时
    futureTask.get(2L, TimeUnit.SECONDS);
    long endTime = System.currentTimeMillis();
    System.out.println("花费的时间:"+(endTime-startTime));
}

程序的执行结果如下

FutureTask的执行结果

查询到了商品的详细信息
远程调用:查询到了商品图片的信息
花费的时间:2017

好了,现在程序的性能已经从3s -> >> 2s了,从业务的最短时间来说,2s已经是完成业务需要的最短时间了,因为本地线程的1s和新线程的1s是并行执行的。

缺点:

会调用get方法造成线程的阻塞,虽然由一个超时的get方法,但是这个超时不是线程的执行时间,而是调用get方法后的超时时间

业务升级

新的需求:

由于用户量的提升,新增了优惠劵、促销等活动。若商品的价格能够触发优惠卷的 话,展现出来的价格就是使用了优惠劵之后的价格。

优惠劵的业务模块是一个单独的业务模块,需要使用微服务调用

对应这个新的需求,如果使用FutureTask来做的话,需要考虑如下问题:

  • 要将商品价格和优惠劵价格进行比较,得到一个显示的价格
  • 商品和优惠劵都是必要的信息,和图片信息不一样
  • 使用FutureTask的话,线程的超时时间不好控制,因为调用get方法可以传入一个超时时间,但是也会阻塞,而且超时时间是从调用get方法的时候开始计算的,即线程前面运行了多少时间无法确认,所以也无法在后面调用get方法

若要解决以上问题,我们想到了一个方法去解决:

  • 用一个线程去执行优惠劵的信息,再用另外一个线程去查询价格信息,再得到两个信息都完成后,对两者进行运算
  • 运算结束后将结果返回给主线程
  • 严格的控制超时时间

这就引入了jdk1.8的新方法CompletableFuture

CompletableFuture

介绍

先看类图

在这里插入图片描述

可以看出来,CompletableFuture是实现了Future的,所以它由Future的全部东南,并且还实例了另外一个接口:ComPletionStage,所以它的功能比Future更加的强大。这也符合设计模式中的开闭原则:扩展新类而不是修改旧类

简要的介绍一些CompletionStage接口:

  • 在一个阶段的任务执行完后可以触发另外一个阶段任务的执行,类是与Linux的管道分隔符 |
  • 提供了函数式编程的能力
  • 一个阶段的执行可以被单个阶段所触发,也可以被多个阶段触发

模拟

用一下代码来解决下上面的需求

private static void m4() throws Exception {
        // map模拟展示数据的vo;数值1表示从数据库中查询出的优惠劵;数值2表示查询商品的详细详细
        Map<Integer,Integer> map = new HashMap<>();
    // 新开一个线程了,这个线程暂停2s
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询结果为1");
            return 1;
        }).whenComplete((v, e) -> {
            // 当上面的运行完后,没有异常则将数据存入map中
            if (e == null) {
                System.out.println("查询成功");
                map.put(1, v);
            }
        }).exceptionally((e) -> {
            e.printStackTrace();
            return null;
        });
		// 主线程中向map中添加数据,暂停1s
        Main.put(map);
    	// 将future阻塞,保证主线程不终止
        future.get();
        System.out.println(map.get(2) - map.get(1));
    }

主线程的添加方法,暂停1s

    public static Map<Integer,Integer> put(Map<Integer,Integer> map){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(2,2);
        return map;
    }

大家发现了,在主线程中也含有get(),这个get也是和Future的get一样会造成阻塞,那么CompletableFuture的优点在哪呢?

大家注意链式编程中的whenComplete方法,这个方法是:当前面运行完后,有了结果就执行这个方法,两个参数值:返回结果和异常

此时,将whenComplete中的方法修改一下,就可以看出优点了:

if (e == null) {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException x) {
        x.printStackTrace();
    }
    System.out.println("查询成功");
    map.put(1, v);
}

这里加了一个1s的延迟,执行结果如下:

查询结果为1
查询成功
两者相减结果:1
执行时间==2099

若使用Future,则需要3s左右的时间

在这里插入图片描述

主要方法

在这里插入图片描述
在这里插入图片描述

关于thenComposethenApply的区别:

两者都是让A执行完后执行B,不同的是

这是thenApply的代码

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            return 100;
        });
        CompletableFuture<CompletableFuture<String>> f = future.thenApply(i -> {
            return CompletableFuture.supplyAsync(() -> {
                return (i * 10) + "";
            });
        });
        System.out.println(f.get().get()); //"1000"

这是thenCompose的代码

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<String> f = future.thenCompose( i -> {
    return CompletableFuture.supplyAsync(() -> {
        return (i * 10) + "";
    });
});
System.out.println("main执行结束");
System.out.println(f.get()); //1000

可以看出,这两个的主要区别是返回的对象不同

  • thenCompose:偏向于对应之前得到的结果,再对其进行异步编排,需要再次使用到CompletableFuture,而得到的结果可以变成一个新的CompletableFuture返回
  • thenApply:偏向于对上面得到的结果进行处理,不用使用CompletableFuture

参考链接

总结

CompletableFuture在可以使用到一些从多处获取数据的地方使用,从而提高程序性能。

附录

函数式编程

接口名称方法名称参数返回值
Runnablerun无参数无返回值
Functionapply1个参数有返回值
Consumeaccept1个参数无返回值
Supplierget没有参数有返回值
据的地方使用,从而提高程序性能。

附录

函数式编程

接口名称方法名称参数返回值
Runnablerun无参数无返回值
Functionapply1个参数有返回值
Consumeaccept1个参数无返回值
Supplierget没有参数有返回值
BiConsumeraccept2个参数无返回值
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值