java8 CompletableFuture 异步执行

本文深入探讨了Java 8中的CompletableFuture类,它为异步编程提供了强大支持。CompletableFuture不仅非阻塞,还提供了丰富的函数式编程能力,包括变换、消耗计算结果以及组合多个异步任务等功能。文章详细介绍了使用Future+线程池的方式实现异步任务,并对比了Future和CompletableFuture的优缺点,讲解了CompletableFuture的各种方法,如thenApply、thenAccept、exceptionally等,以及它们在处理异常和结果时的不同。最后,文章提到了使用CompletableFuture的注意事项,强调了异步任务的管理和结果获取的正确方式。
摘要由CSDN通过智能技术生成

一:简介

在这里插入图片描述

      CompletableFuture 是 Java 8 引入用于支持异步编程和非阻塞操作的类。CompletableFuture类实现了CompletionStageFuture接口。Future是Java 5添加的类,用来描述一个异步计算的结果,但是获取一个结果时方法较少,要么通过轮询isDone,确认完成后,调用get()获取值,要么调用get()设置一个超时时间。但是这个get()方法会阻塞住调用线程,这种阻塞的方式显然和我们的异步编程的初衷相违背。

为了解决这个问题,JDK吸收了guava的设计思想,加入了Future的诸多扩展功能形成CompletableFuture。

Java8新增的CompletableFuture类提供了非常强大的Future的扩展功能,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了组合多个异步任务的方法。并且CompletableFuture是非阻塞性的,就是在主线程之外创建一个独立的线程,用以运行一个非阻塞的任务,然后将结果通知主线程。通过这种方式,主线程不用为了任务的完成而阻塞,极大提升了程序的性能。

CompletionStage是一个接口,从命名上看得知是一个完成的阶段,它里面的方法也标明是在某个运行阶段得到了结果之后要做的事情 。

Completable:可完成

Future:未来/将来

这两个单词体现了它设计的目的:提供一种可完成的异步计算。

在这里插入图片描述

二:Future + 线程池

      Future是Java5引入的新接口,提供了异步并行计算的能力。如果主线程需要执行一个耗时的计算任务,我们就可以通过Future把这个任务放到异步线程中执行,主线程继续处理其他任务,处理完成后,再通过Future获取计算结果。

异步执行一个任务时,需要用线程池Executor或者ThreadPoolExecutor(提倡)去创建,有两种方式:

  • 如果不需要有返回值, 任务继承Thread类或实现Runnable接口;
  • 如果需要有返回值,任务实现Callable接口,调用ThreadPoolExecutorsubmit方法执行任务,再使用Future.get()获取任务结果。
public class PlayerInfo {
    private String name;
    private int age;
}

public class PlayerInfoService {
    public PlayerInfo getPlayerInfo() throws InterruptedException {
        Thread.sleep(300);//模拟调用耗时
        return new PlayerInfo("Kobe", 32);
    }
}
public class MedalInfo {
    private String medalCategory;
    private int medalNum;
}

public class MedalInfoService {
    public MedalInfo getMedalInfo() throws InterruptedException {
        Thread.sleep(500); //模拟调用耗时
        return new MedalInfo("MVP", 1);
    }
}

1:使用Future+线程池的方式实现异步任务

 public static void main(String[] args) throws InterruptedException, ExecutionException {
            // 1. 创建线程池
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                    2,
                    2,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingDeque<>(2),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy());

            // 2. 任务加入线程池
            PlayerInfoService playerInfoService = new PlayerInfoService();
            MedalInfoService medalInfoService = new MedalInfoService();

            long startTime = System.currentTimeMillis();

            FutureTask<PlayerInfo> playerInfoFutureTask = new FutureTask<>(new Callable<PlayerInfo>() {
                @Override
                public PlayerInfo call() throws Exception {
                    return playerInfoService.getPlayerInfo();
                }
            });
            threadPoolExecutor.submit(playerInfoFutureTask);

            Thread.sleep(400);

            FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>(){
                @Override
                public MedalInfo call() throws Exception {
                    return medalInfoService.getMedalInfo();
                }
            });
            threadPoolExecutor.submit(medalInfoFutureTask);

            PlayerInfo playerInfo = playerInfoFutureTask.get();
            MedalInfo medalInfo = medalInfoFutureTask.get();
            System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");

            // 3. 关闭线程池
            threadPoolExecutor.shutdown();
        }

当然,创建FutureTask的可以使用lambda表达式或方法引用,使代码更简洁,如下:

    FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>(){
        @Override
        public MedalInfo call() throws Exception {
            return medalInfoService.getMedalInfo();
        }
    });
    
    // lambda表达式
    FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(() -> medalInfoService.getMedalInfo());
    
    // 方法引用
    FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(medalInfoService::getMedalInfo);

运行结果:

总共用时905ms

如果是在主线程串行执行的话,耗时大约为300+500+400=1200ms。可见,future+线程池的异步方式,提高了程序的执行效率。但是,Future对于结果的获取,只能通过阻塞或者轮询的方式得到任务的结果。

  • Future.get()就是阻塞调用,在线程获取结果之前get方法会一直阻塞。阻塞的方式和异步编程的设计理念相违背。
  • Future提供了一个isDone方法,可以在程序中轮询这个方法查询执行结果。轮询的方式会耗费CPU资源。

轮询——在计算机网络中,轮询(Polling)是一种通信方式,其中一个节点(通常是客户端)定期发送请求到另一个节点(通常是服务器)以获取数据或状态。这种方式的缺点在于,即使没有可用的数据,客户端也会持续不断地发送请求,这会消耗大量的CPU资源。此外,如果轮询的间隔设置得太短,可能会导致网络拥塞,甚至可能影响实时性。因此,轮询并不是一种高效的通信方式,特别是在需要高性能和低延迟的应用中。

2:CompletableFuture来实现

public class CompletableFutureTest {
    public static void main(String[] args) throws Exception {
        PlayerInfoService playerInfoService = new PlayerInfoService();
        MedalInfoService medalInfoService = new MedalInfoService();
        long startTime = System.currentTimeMillis();
        CompletableFuture<PlayerInfo> userInfoCompletableFuture = CompletableFuture.supplyAsync(() -> playerInfoService.getPlayerInfo());
        Thread.sleep(400);
        CompletableFuture<MedalInfo> medalInfoCompletableFuture = CompletableFuture.supplyAsync(() -> medalInfoService.getMedalInfo());
        PlayerInfo playerInfo = userInfoCompletableFuture.get();
        MedalInfo medalInfo = medalInfoCompletableFuture.get();
        System.out.println("共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }  
}

相比于Future

  • CompletableFuture的链式编程使得代码更加简洁易读。
  • CompletableFuture提供的supplyAsync方法,不需要手动创建和关闭线程,线程池自
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值