Feture和CompletableFutire使用

Feture和CompletableFutire使用文章介绍了如何使用 FutureTask 创建异步任务 并指出 get 方法是同步阻塞的 建议在 Web 应用中设置超时时间或使用轮询

大家好,欢迎来到IT知识分享网。

使用FutureTask创建异步任务

Future是一个接口,常见的子实现类主要是FutureTask类。Future接口是在JDK5出现的

image-20221021111217779

通过Thread的构造函数可以知道,需要接收一个实现了Runnable接口的类,但是没有存在接收FutureTask的相关构造函数,但是从上面的类图来看,FutureTask实际上也是实现了Runnable接口的,所以FutureTask的实例也是可以作为Thread的构造函数中的参数进行入参的

public class Demo1 { 
    Logger log= LoggerFactory.getLogger(Demo1.class); @Test public void futureTask() throws ExecutionException, InterruptedException { 
    FutureTask<String> futureTask=new FutureTask<>(()->"hello FutureTask"); futureTask.run(); log.info("FutureTask执行结果:{}",futureTask.get()); } } 

通过上诉案例我们可以看到,最后的结果可以通过get方法获取,但是需要注意的是,get方法是同步阻塞的,也就是说如果我们不是放在最后那么会阻塞当前主线程,直到拿到了结果才会继续往下走,如果这是在Web应用中,如果非要等到结果那么前端可能就需要等待更长的时间,所以一般都会设置超时时间。或者是使用轮询的方式

//默认等到三秒中,三秒过后抛出超时异常 futureTask.get(3,TimeUnit.SECONDS); //使用轮询的方式 while(true){ 
    //判断线程是否执行完成 if(futureTask.isDone()){ 
    log.info("异步线程执行完成,结果为: {}",futureTask.get()); break; } else { 
    TimeUnit.SECONDS.sleep(1); } } 

在上述案例中我们是直接创建的新的线程,但是实际上线程的整个生命周期时间是相对较长的,并且创建线程是开销比较大的,所以一般我们会结合使用线程池的方式来进行创建。

结合使用线程池

下述案例中使用了apache的秒表工具进行了耗时的统计,需要引入相应的依赖

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version> </dependency> 
public class Demo2 { 
    private final Logger log= LoggerFactory.getLogger(Demo2.class); / * 测试不使用线程池时的执行时间,1019毫秒 */ @Test public void doNotUseThreadPool() throws ExecutionException, InterruptedException { 
    FutureTask<String> future1=new FutureTask<>(()->{ 
    TimeUnit.MILLISECONDS.sleep(500); return "future1"; }); FutureTask<String> future2=new FutureTask<>(()->{ 
    TimeUnit.MILLISECONDS.sleep(500); return "future2"; }); StopWatch watch=StopWatch.createStarted(); future1.run(); future2.run(); future1.get(); future2.get(); log.info("执行时间: {} 毫秒",watch.getTime()); } / * 测试使用线程池时的执行时间,505毫秒 */ @Test public void useThreadPool() throws ExecutionException, InterruptedException { 
    ExecutorService executorService= Executors.newFixedThreadPool(2); FutureTask<String> future1=new FutureTask<>(()->{ 
    TimeUnit.MILLISECONDS.sleep(500); return "future1"; }); FutureTask<String> future2=new FutureTask<>(()->{ 
    TimeUnit.MILLISECONDS.sleep(500); return "future2"; }); StopWatch watch=StopWatch.createStarted(); executorService.execute(future1); executorService.execute(future2); future1.get(); future2.get(); log.info("执行时间: {} 毫秒",watch.getTime()); } } 

使用CompletableFuture执行异步任务

image-20221021104527160

对于使用CompleTableFuture类一般不会直接就new一个对象,而是通过它的四个静态方法来获取对应的实例

//无返回值的实例 public static CompleTablrFuture<Void> runAsync(Runnable runnable); //无返回值,并且指定了线程池的实例 public static CompleTableFuture<Void> runAsync(Runnable runnable,Executor executor); //有返回值的实例 public static CompleTableFuture<T> supplyAsync(Supplier<T> supplier); //有返回值并且指定了线程池的实例 public static CompleTableFuture<T> supplyAsync(Supplier<T> supplier,Executor executor); 

接下来使用简单的应用看看这几个方法的实际应用

public class CompleTableFuturePractice{ 
     public static void main(String[] args){ 
     } public static void runAsyncNotThreadPool(){ 
     CompleTableFuture<Void> com=CompleTableFuture.runAsync(()->{ 
     System.out.println("使用无返回值不指定线程池的方法"); }); System.out.println("查看无返回值时取到的参数是什么:"+com.get()) } public static void runAsyncThreadPool(){ 
     } } 
join和get的区别

在使用get获取返回值时,我们需要手动抛出异常或者使用try-catch块进行异常的处理,但是join不需要这么做,我们可以不用做任何异常的处理就可以获取到值

常见的API使用
  • 获取运算结果
    方法 解释 示例
    get() 阻塞获取结果并抛出异常 compleTableFuture.get()
    get(long timeout,TimeUtil util) 阻塞获取结果,当等待一定时间之后还没有结果则抛出超时异常 compleTableFuture.get(2L,TimeUnit.SECONDS
    join() 阻塞获取结果,但是不会想get()那样抛异常 compleTableFuture.join()
    getNow(T valueIfAbsent) 当调用此方法获取结果时,如果计算完成则将结果返回,否则返回valueIfAbsent的备用值,该方式获取结果不阻塞 compleTableFuture.getNow(“test”)泛型取决于你的实例是什么泛型的
    complete(T value) 返回类型是布尔值,如果计算结果完成了,那么久会返回false,如果没有完成则返回true,并且将计算结果设置为value,并且需要使用join或者是其他的方式去获取值,获取到的值就是value System.out.println(compleTableFuture.complete(“test”));
    System.out.println(compleTableFuture.join());

    案例使用

    public class Demo1 { 
           private final Logger log= LoggerFactory.getLogger(Demo1.class); @Test public void get() throws ExecutionException, InterruptedException { 
           CompletableFuture<String> com=CompletableFuture.supplyAsync(()->"get"); log.info("调用get方法获取返回值: {}",com.get()); } @Test public void getTimeOut() throws ExecutionException, InterruptedException, TimeoutException { 
           CompletableFuture<String> com=CompletableFuture.supplyAsync(()->{ 
           try { 
           Thread.sleep(2000); } catch (InterruptedException e) { 
           throw new RuntimeException(e); } return "getTimeOut"; }); log.info("使用get方法获取到的值是: {}",com.get(3, TimeUnit.SECONDS)); } @Test public void join(){ 
           CompletableFuture<String> com=CompletableFuture.supplyAsync(()->"join"); log.info("使用join方法获取值: {}",com.join()); } @Test public void getNow(){ 
           CompletableFuture<String> com=CompletableFuture.supplyAsync(()->{ 
           try { 
           Thread.sleep(3000); } catch (InterruptedException e) { 
           throw new RuntimeException(e); } return "执行完成"; }); log.info("使用getNow方法获取到的值: {}",com.getNow("线程未执行完成使用备用值")); } @Test public void complete() throws InterruptedException { 
           CompletableFuture<String> com=CompletableFuture.supplyAsync(()->{ 
           try { 
           Thread.sleep(3000); } catch (InterruptedException e) { 
           throw new RuntimeException(e); } return "线程执行完成!"; }); log.info("使用Complete计算结果是否完成{},获取执行结果:{}",com.complete("线程未完成!"),com.join()); } } 
  • 对计算结果进行处理
    方法 解释
    thenApply 将计算结果传递到下一步,有点类似于whenComplete,但是这个这个只接收一个参数
    handler 将计算结果传递到下一步,同时还传递上一步结果的异常,如果异常为空则表示没有异常

    案例使用

    public class Demo2 { 
           private final Logger log= LoggerFactory.getLogger(Demo2.class); private final ExecutorService executorService= Executors.newSingleThreadExecutor(); @Test public void thenApply(){ 
           CompletableFuture<Integer> com = CompletableFuture.supplyAsync(() -> 1).thenApply(value -> value + 1); log.info("使用thenApply方法: {}",com.join()); } @Test public void handler(){ 
           CompletableFuture<Integer> com = CompletableFuture.supplyAsync(() -> { 
           int i = 10 / 0; return 1; }).handle((value, exception) -> { 
           if (exception != null) { 
           log.error(exception.getMessage()); return 0; } else{ 
           return value + 1; } }); log.info("使用handler方法: {}", com.join()); } } 
  • 对结果进行消费
    方法
    thenAccept 该方法接收一个消费性函数,即我们在使用有返回值的compleTableFuture时,如果使用该方法之后那么结果会经过传入的函数进行消费不会返回结果了,即B需要A的返回结果,但是B不返回结果

    案例使用

    public class Demo3 { 
           private final Logger log= LoggerFactory.getLogger(Demo3.class); @Test public void thenAccept(){ 
           CompletableFuture<Void> com=CompletableFuture.supplyAsync(()->1).thenAccept(value->{ 
           log.info("上一步执行结果: {}",value); }); log.info("使用thenAccept消费结果之后获取到的值: {}",com.join()); } } 
  • 对计算结果速度的选用
    方法 解释
    applyToEither 由一个compleTableFuture的实例进行调用,并且传入的参数有两个,一个是另一个compleTableFuture实例,另一个就是其他函数,对两个实例的计算结果进行选取,谁块用谁的,然后函数里面传入的参数就是最快的哪个的结果

    案例使用

    public class Demo4{ 
           } 
  • 对结果进行合并
    方法 解释
    thenCombine 由一个compleTableFuture的实例进行调用的,传入另一个compleTableFuture实例和一个函数,函数的参数就是两个实例的计算结果,然后进行相应的操作

JDK中常见的函数式接口

函数式接口名称 方法名称 参数 返回值
Runnable run 无参数 无返回值
Function apply 1个参数 有返回值
Consumer accept 1参数 无返回值
Supplier get 无参数 有返回值
BiConsumer accept 2个参数 无返回值

Function:函数型接口,有一个可以将任意数据类型转换为其他任意数据类型的接口

Supplier:供给型接口,有一个可以提供任意数据类型的方法

Consumer:消费性接口,有一个可以消费任意数据类型的方法

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/137611.html

(0)
上一篇 2025-06-19 14:20
下一篇 2025-06-19 14:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信