战狂粗人张

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  228 随笔 :: 0 文章 :: 12 评论 :: 20万 阅读
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

一.Future是什么

Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

 

二.Future能做什么

Future在处理异步调用和并发处理时非常有用。

创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。

这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。

如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

Future接口是长时间运行方法异步处理的理想选择。这使我们能够在等待Future封装的任务完成时执行一些其他事情。

利用Future的异步性质的操作示例如下:

(1)计算密集型过程(数学和科学计算)。

(2)操纵大数据结构(大数据)。

(3)远程方法调用(下载文件,抓取HTML,Web服务)。

 

三.Future原理

Future 是一 Java 1.5 带进来的产物。Future 本身不代表着多线程,而是代表着需异步计算的结果,将来的一个期待,至于后来真正的结果不可知。

在此之前想要获得一个 Runnable 在其他线程中的计算结果颇费波折,有了 Future 加之它身后的 Callable 一切就变得简单了。

对比一下 Java 1.5 前后的下面几个概念:

(1)Callable 相当于之前的 Runnable, 只是 Callable 是有返回值的;

(2)ExecuteService.submit(callable): Future 就类似于之前的 Thread(runnable)

只是前者 submit 后立即执行,通过 get() 获得结果,后者用 start() 方法启动,runnable 是没有结果的。

如果不想关心 Future 的结果也能 ExecuteService.submit(runnable),只有 callable 被提交(到线程池) 后返回的 Future 才可能会有结果, 所以下面的代码永远等不到结果。

 

Future接口定义了主要的5个接口方法,有RunnableFuture和SchedualFuture继承这个接口,以及CompleteFuture和ForkJoinTask继承这个接口。

 

1、RunnableFuture

这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。

这个接口都实现类是FutureTask,一个可取消的异步计算,这个类提供了Future的基本实现,后面我们的demo也是用这个类实现,

它实现了启动和取消一个计算,查询这个计算是否已完成,恢复计算结果。计算的结果只能在计算已经完成的情况下恢复。

如果计算没有完成,get方法会阻塞,一旦计算完成,这个计算将不能被重启和取消,除非调用runAndReset方法。

FutureTask能用来包装一个Callable或Runnable对象,因为它实现了Runnable接口,而且它能被传递到Executor进行执行。为了提供单例类,这个类在创建自定义的工作类时提供了protected构造函数。

 

2、SchedualFuture

这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果。

 

3、CompleteFuture

一个Future类是显示的完成,而且能被用作一个完成等级,通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时,只有一个能够成功。

 

4、ForkJoinTask

基于任务的抽象类,可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体,但是相对于线程实体是轻量级的。

大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来,以某些使用限制为代价。

ForkJoinTask的主要特征是它通常会产生新的子任务,作为完成其主要任务所需的工作的一部分。它通过调用fork()生成新任务,并使用join()收集所有结果,从而得到类的名称。

有两个实现ForkJoinTask的抽象类:RecursiveTask,它在完成时返回一个值,而RecursiveAction则不返回任何内容。顾名思义,这些类将用于递归任务,例如文件系统导航或复杂的数学计算。

 

5、总结

单使用Future是不方便的,其主要原因有:一方面没有提供方法去判断第一个完成的任务;

另一方面是 Future没有提供Callback机制,只能通过阻塞的get方法去获取结果。

针对第一个问题,JAVA引入了CompletionService接口。

(1)CompletionService整合了Executor和BlockingQueue的功能。CompletionService维护一个保存Future对象的BlockingQueue。

只有当这个Future对象状态是结束的时候,才会加入到这个Queue中。这样就确保执行时间较短的任务率先被存入队列中。

与Future流程的不同主要是: 1. callable任务提交后,exexute方法执行的是封装成QueueingFuture的任务对象。

QueueingFuture是FutureTask的子类,重写了done方法,在task执行完成之后将当前future添加到阻塞队列completionQueue。

 

(2)CompletableFuture是JDK1.8新增的的类,提供了非常强大的Future的扩展功能。可以对多个异步处理进行编排,实现更复杂的异步处理。

它能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。大家可以参考CompletableFuture的API了解它提供的功能。

在实际项目中,需要根据具体的业务场景选择合适的Future工具类来实现异步编程。如果项目中使用Java 8,推荐使用CompletableFuture类,它提供了更多的异步控制。

如果使用之前版本,可以使用Guava等框架提供的Future工具类。

 

四.Future使用

需求场景:

等早餐过程中,包子需要3秒,凉菜需要1秒,普通的多线程需要四秒才能完成。

先等凉菜,再等包子,因为等凉菜时,普通多线程启动start()方法,执行run()中具体方法时,没有返回结果,所以如果要等有返回结果,必须是要1秒结束后才知道结果。

普通多线程:

复制代码
public class BumThread extends Thread{
    
    @Override
    public void run() {
        try {
            Thread.sleep(1000*3);
            System.out.println("包子准备完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
}
复制代码
复制代码
public class ColdDishThread extends Thread{
    
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("凉菜准备完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
}
复制代码
复制代码
public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        
        // 等凉菜 -- 必须要等待返回的结果,所以要调用join方法
        Thread t1 = new ColdDishThread();
        t1.start();
        t1.join();
        
        // 等包子 -- 必须要等待返回的结果,所以要调用join方法
        Thread t2 = new BumThread();
        t2.start();
        t2.join();
        
        long end = System.currentTimeMillis();
        System.out.println("准备完毕时间:"+(end-start));
    }
复制代码

采用Future模式:

复制代码
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis(); // 等凉菜 Callable ca1 = new Callable(){ @Override public String call() throws Exception { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "凉菜准备完毕"; } }; FutureTask<String> ft1 = new FutureTask<String>(ca1); new Thread(ft1).start(); // 等包子 -- 必须要等待返回的结果,所以要调用join方法 Callable ca2 = new Callable(){ @Override public Object call() throws Exception { try { Thread.sleep(1000*3); } catch (InterruptedException e) { e.printStackTrace(); } return "包子准备完毕"; } }; FutureTask<String> ft2 = new FutureTask<String>(ca2); new Thread(ft2).start(); System.out.println(ft1.get()); System.out.println(ft2.get()); long end = System.currentTimeMillis(); System.out.println("准备完毕时间:"+(end-start)); }
复制代码

 

posted on   战狂粗人张  阅读(279)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示