CompletableFuture使用大全,简单易懂

CompletableFuture是高级的多线程功能,支持自定义线程池和系统默认的线程池,是多线程,高并发里面,常常须要用到的比直接建立线程,要简单易用的方法。bash

本文主要内容以下:

  • CompletableFuture基本简述
  • API分类与记忆规律
  • 建立CompletableFuture
  • 取值与状态测试
  • 控制CompletableFuture执行
  • CompletableFuture行为接续

CompletableFuture基本概述

CompletableFuture主要是用于异步调用,内部封装了线程池,能够将请求或者处理过程,进行异步处理。建立线程有3种方式,直接继承Thread、实现Runnable接口、实现Callable接口。以生活中的一个例子来讲明异步行为:电饭煲蒸饭。多线程

之前呀,都是大锅饭,放上米,放上水,而后须要不断地加柴火,人要看着火,具体何时煮熟,也得偶尔打开看看,看看开没开锅,煮没煮熟。这种就是没有任何通知方式,没有返回值的Runnable,只管煮饭,煮没煮熟须要本身判断。并发

一个老板发现了这个商机,说能不能作一个东西,不用人一直看着,自动就能把米饭作好,因此电饭煲就出现了。 初代电饭煲的出现,算是解放了人力,不再用看着火了,方便了不少,本身能够去作点其余的事情,热个牛奶,剪个鸡蛋什么的,可是至于饭何时熟,还得本身隔一段时间就得过去看一看。这就是Future的方式,虽然任务是异步执行的,可是要想得到这个结果,还得须要本身取。app

时间继续推动,这个老板又有了新的想法,每隔一段时间,看看饭熟没熟仍是有点浪费我看电视的时间,这个电饭煲能不能作好了,告诉我呢,这样我就直接来吃就好了。所以就有了这种能够预定、能够定时、能够保温的高级电饭煲。这个就对应着CompletableFuture,全部事情都是能够自动完成,便可以在完成以后,回调通知,也能够本身去等待。异步

  • Runnable就是没有返回结果的行为。
  • Callable是有返回结果的行为。
  • Future 异步封装Callable和Runnable,委托给线程池执行后,须要取回执行的结果
  • CompletableFuture 封装了Future,使其拥有了回调的功能,在某个行为完成以后,能够继续进行下一个动做。
示例代码:
public static void main(String[] args){
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        System.out.println("电饭煲开始作饭");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "白米饭";
    }).thenAccept(result -> {
        System.out.println("开始吃米饭");
    });

    System.out.println("我先去搞点牛奶和鸡蛋");
    future.join();
}

结果输出:
电饭煲开始作饭
我先去搞点牛奶和鸡蛋
开始吃米饭
复制代码

这样就能够一边等待米饭煮熟,一边去作其余事情。ide

API方法分类与记忆规律

CompletableFuture提供了方法大约有50多个,单纯一个个记忆,是很麻烦的,所以将其划分为如下几类:高并发

建立类

  • completeFuture 能够用于建立默认返回值
  • runAsync 异步执行,无返回值
  • supplyAsync 异步执行,有返回值
  • anyOf 任意一个执行完成,就能够进行下一步动做
  • allOf 所有完成全部任务,才能够进行下一步任务

状态取值类

  • join 合并结果,等待
  • get 合并等待结果,能够增长超时时间;get和join区别,join只会抛出unchecked异常,get会返回具体的异常
  • getNow 若是结果计算完成或者异常了,则返回结果或异常;不然,返回valueIfAbsent的值
  • isCancelled
  • isCompletedExceptionally
  • isDone

控制类 用于主动控制CompletableFuture的完成行为

  • complete
  • completeExceptionally
  • cancel

接续类 CompletableFuture 最重要的特性,没有这个的话,CompletableFuture就没意义了,用于注入回调行为。

  • thenApply, thenApplyAsync
  • thenAccept, thenAcceptAsync
  • thenRun, thenRunAsync
  • thenCombine, thenCombineAsync
  • thenAcceptBoth, thenAcceptBothAsync
  • runAfterBoth, runAfterBothAsync
  • applyToEither, applyToEitherAsync
  • acceptEither, acceptEitherAsync
  • runAfterEither, runAfterEitherAsync
  • thenCompose, thenComposeAsync
  • whenComplete, whenCompleteAsync
  • handle, handleAsync
  • exceptionally

上面的方法不少,咱们不必死记硬背,按照以下规律,会方便不少,记忆规则:测试

  1. 以Async结尾的方法,都是异步方法,对应的没有Async则是同步方法,通常都是一个异步方法对应一个同步方法。
  2. 以Async后缀结尾的方法,都有两个重载的方法,一个是使用内容的forkjoin线程池,一种是使用自定义线程池
  3. 以run开头的方法,其入口参数必定是无参的,而且没有返回值,相似于执行Runnable方法。
  4. 以supply开头的方法,入口也是没有参数的,可是有返回值
  5. 以Accept开头或者结尾的方法,入口参数是有参数,可是没有返回值
  6. 以Apply开头或者结尾的方法,入口有参数,有返回值
  7. 带有either后缀的方法,表示谁先完成就消费谁

记住上面几条,基本上就能够记住大部分的方法,剩下的其余方法,就能够单独记忆了。ui

建立CompletableFuture

建立CompletableFuture,其实就是将咱们要煮的米饭,委托给电饭煲;要煮米饭,咱们要准备这么几件事情,其一咱们要制定制做米饭的方式,其二,咱们要指定电饭煲。除此以外,咱们也能够委托其余的事情,最后能够经过all或者any进行组合。this

// 异步任务,无返回值,采用内部的forkjoin线程池
CompletableFuture c1 = CompletableFuture
.runAsync(()->{System.out.println("打开开关,开始制做,就不用管了")});

// 异步任务,无返回值,使用自定义的线程池
CompletableFuture c11 = CompletableFuture
.runAsync(()->{System.out.println("打开开关,开始制做,就不用管了")},newSingleThreadExecutor());

// 异步任务,有返回值,使用内部默认的线程池
CompletableFuture<String> c2 = CompletableFuture
.supplyAsync(()->{System.out.println("清洗米饭");return "干净的米饭";});

// 只要有一个完成,则完成,有一个抛出异常,则携带异常
CompletableFuture.anyOf(c1,c2);

// 必须等待全部的future所有完成才能够
CompletableFuture.allOf(c1,c2);

复制代码

取值与状态

经常使用的是下面的这几种
// 不抛出异常,阻塞的等待
future.join()
// 有异常则抛出异常,阻塞的等待,无限等待
future.get()
// 有异常则抛出异常,最长等待1个小时,一个小时以后,若是尚未数据,则异常。
future.get(1,TimeUnit.Hours)

复制代码

控制CompletableFuture执行

3种方式:
// 完成
future.complete("米饭");
// 异常
future.completeExceptionally();
// 取消,参数并无实际意义,没任何卵用。
future.cancel(false);

复制代码

接续行为,用来描述上一件事作完以后,该作什么

接续方式有不少种,能够总结为一下三类:

  1. CompletableFuture + (Runnable,Consumer,Function)
  2. CompletableFuture + CompletableFuture
  3. CompletableFuture + 处理结果

接续方式1

CompletableFuture future = CompletableFuture.supplyAsync(()->{
    System.out.println("投放和清洗制做米饭的材料");
    return "干净的没有新冠病毒的大米";
}).thenAcceptAsync(result->{
    System.out.println("通电,设定模式,开始煮米饭");
}).thenRunAsync(()->{
    System.out.println("米饭作好了,能够吃了");
})

复制代码

接续方式2

假如蒸米饭和、热牛奶、炒菜等已是3个不一样的CompletableFuture,可使用接续方式2,将两个或者多个CompletableFuture组合在一块儿使用。

CompletableFuture rice = CompletableFuture.supplyAsync(()->{
    System.out.println("开始制做米饭,并得到煮熟的米饭");
    return "煮熟的米饭";
})

//煮米饭的同时呢,我又作了牛奶
CompletableFuture mike = CompletableFuture.supplyAsync(()->{
    System.out.println("开始热牛奶,并得到加热的牛奶");
    return "加热的牛奶";
});

// 我想两个都好了,才吃早饭,thenCombineAsync有入参,有返回值
mike.thenCombineAsync(rice,(m,r)->{
    System.out.println("我收获了早饭:"+m+","+r);
    return m+r;
})
// 有入参,无返回值
mike.thenAcceptBothAsync(rice,(m,r)->{
   System.out.println("我收获了早饭:"+m+","+r); 
});
// 无入参,入参会之
mike.runAfterBothAsync(rice,()->{
   System.out.println("我收获了早饭"); 
});

// 或者直接链接两个CompletableFuture
rice.thenComposeAsync(r->CompletableFuture.supplyAsync(()->{
    System.out.println("开始煮牛奶");
    System.out.println("同时开始煮米饭");
    return "mike";
}))

复制代码

接续方式3

若是咱们只想作结果处理,也没有其余的接续动做,而且咱们想要判断异常的状况,那么能够用接续方式3

whenCompleteAsync:处理完成或异常,无返回值
handleAsync:处理完成或异常,有返回值

CompletableFuture.supplyAsync(()->{
    System.out.println("开始蒸米饭");
    return "煮熟的米饭";
}).whenCompleteAsync((rich,exception)->{
    if (exception!=null){
        System.out.println("电饭煲坏了,米饭没作熟");
    }else{
        System.out.println("米饭熟了,能够吃了");
    }
})
// 有返回值
CompletableFuture.supplyAsync(()->{
    System.out.println("开始蒸米饭");
    return "煮熟的米饭";
}).handleAsync((rich,exception)->{
    if (exception!=null){
        System.out.println("电饭煲坏了,米饭没作熟");
    }else{
        System.out.println("米饭熟了,能够吃了");
    }
    return "准备冷一冷再吃米饭";
})

// 异常处理
CompletableFuture.supplyAsync(()->{
    System.out.println("开始蒸米饭");
    return "煮熟的米饭";
}).handleAsync((rich,exception)->{
    if (exception!=null){
        System.out.println("电饭煲坏了,米饭没作熟");
    }else{
        System.out.println("米饭熟了,能够吃了");
    }
    return "准备冷一冷再吃米饭";
}).exceptionally((exception)->{
    // 前置动做必须的是一个又返回值的操做,不能是那种返回值的那种
    return "";
});

复制代码

CompletableFuture用了以后,才以为这个东西,确实好用一些,不经意间就作成了异步处理,并且支持自定义的线程池,若是结合stream,能够轻松地实现多线程并发处理。

List<CompletableFuture<YoutubeVideoEntity>> futures = subVideosList.stream()
        .map(item ->
                CompletableFuture.supplyAsync(() -> this.getRetry(item)
                        , ThreadPoolHolder.BG_CRAWLER_POOL)
        ).collect(Collectors.toList());

List<YoutubeVideoEntity> videoEntities = futures.stream().map(CompletableFuture::join)
.filter(item -> item != null && item.getVideoId() != null).collect(Collectors.toList());
复制代码
相关文章
相关标签/搜索