如何实现线程返回值——FutureTask

本文介绍了Java中的FutureTask类,它用于处理并返回异步任务的结果。通过FutureTask,可以在计算结束后获取结果,或者在计算未完成时阻塞等待。文章提供了一个将大计算任务拆分为小任务并发执行的例子,并分析了FutureTask的构造和工作原理。

一、介绍

FutureTask类位于java.util.concurrent包中,用于处理并返回异步任务结果。
FutureTask类源码注释:“一个可取消的异步计算。这个类实现了Future的基本方法:开始,取消计算、查询计算是否结束、返回计算结果。计算结果只能在计算结束的时候返回;如果计算尚未结束,get方法就会阻塞等待。一旦计算完毕,就不能取消或重启计算。”
最关键的一句就是,它的get方法会等到计算完毕才返回结果,非常适合用来将大的计算任务拆分成一个个独立的小的计算任务,用几个线程同时计算小任务,最终返回并叠加出大任务的结果。

二、简单的例子

假设要计算20万以内的和,除了暴力法,还可以将其分成4份:1-50000、50000-100000…的和运算,除去开启线程的成本,理想情况下,用时减少为原来的1/4。
首先,我们创建一个继承Callable类的类(SegmentTask),实现call方法,用于本区间的计算:

public class SegmentTask implements Callable<Integer> {
    //第几分段
    private int order;
    //要求小于n,现在要求小于20万
    private int n;
    //总共有几个分段
    private int p;
    public SegmentTask(int order, int n, int p) {
        this.order = order;
        this.n = n;
        this.p = p;
    }

    @Override
    public Integer call() throws Exception {
       //计算求和
       return sum();
    }
}

然后,在主线程中分出4个任务,分别计算4个区间的和:

public static void solution(int n) {
        int p = 4;
        int sum = 0;

        FutureTask<Integer> ft1 = new FutureTask<>(new SegmentTask(1,n,p));
        FutureTask<Integer> ft2 = new FutureTask<>(new SegmentTask(2,n,p));
        FutureTask<Integer> ft3 = new FutureTask<>(new SegmentTask(3,n,p));
        FutureTask<Integer> ft4 = new FutureTask<>(new SegmentTask(4,n,p));

        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        Thread t3 = new Thread(ft3);
        Thread t4 = new Thread(ft4);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        try {
            sum = ft1.get()+ft2.get()+ft3.get()+ft4.get();
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

三、源码分析

而为啥上述示例要在实现Callable接口,在里面写核心计算逻辑,再传给FutureTask呢?那就要看看FutureTask的源码结构了。
FutureTask类图.png
FutureTask实现了Runnable和Future接口,其中实现Runnable接口可创建线程(常见用法:交给Thread或Executor执行):

public interface Runnable {
    /**
     * 实现Runnable接口可以创建线程,开启线程会使该对象的run方法在线程内部被调用
     */
    public abstract void run();
}

Future接口则是定义了异步计算的方法:

public interface Future<V> {

    /**
     * 尝试取消任务,如果该任务已经完成、已经取消或者因为其他原因不能取消的话就会执行失败。执行成功的话,
     * 如果该任务还没开始,那这个任务就不会跑起来;如果该任务已经在跑了,那么mayInterruptIfRunning参数会决定是否中断
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 返回该任务是否在完成前被取消了
     */
    boolean isCancelled();

    /**
     * 返回该任务是否已经完成了
     */
    boolean isDone();

    /**
     * 等到计算完成才返回结果
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 等到计算完成才返回结果(多了超时设置参数)
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

接下来看看FutureTask的构造方法:

    /**
     * 创建FutureTask对象,执行传进来的Callable对象
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

而Callable接口和Runnable一样,都是设计给那些可以给线程执行的类对象,但Callable可以返回结果,Runnable不行。
所以整个过程就是这样,将期盼返回值的核心逻辑在Callable的call方法实现,再由FutureTask封装成一个任务,交给Thread子线程执行,后返回结果给主线程。

### Java 中获取线程返回值的方法 在 Java 中,传统的 `Thread` 类和实现了 `Runnable` 接口的对象其 `run()` 方法均不支持返回值。为了能够从线程中获得计算结果,可以采用实现 `Callable<V>` 接口的方式[^1]。 #### 使用 Callable 和 Future 实现返回值线程 `Callable<V>` 是一个类似于 `Runnable` 的接口,但它允许定义一个带有泛型参数 V 表示返回类型的 `call()` 方法。此方法可以在新启动的线程内执行并最终返回某个结果给调用者。通常情况下,会配合 `Future<V>` 来接收由 `Callable.call()` 返回的数据;`Future.get()` 可阻塞当前线程直到关联的任务完成,并取回该任务的结果[^3]。 下面是一个简单的例子展示如何利用这些特性: ```java import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Example { public static void main(String[] args) { // 创建一个实现了 Callable<String> 的实例 Callable<String> task = new TestCallable(); // 将 Callable 装入 FutureTask 容器以便于后续操作 FutureTask<String> futureTask = new FutureTask<>(task); // 启动新的线程去执行这个任务 Thread thread = new Thread(futureTask); thread.start(); try { // 阻塞等待直至得到结果 System.out.println("Result from the other thread: " + futureTask.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } private static class TestCallable implements Callable<String> { @Override public String call() throws Exception { // 模拟耗时处理... Thread.sleep(3000L); return "This is a result string."; } } } ``` 这段代码展示了怎样创建一个自定义的 `Callable` 对象并通过 `FutureTask` 提交至独立线程运行,在主线程里通过 `futureTask.get()` 获取子线程结束后的输出信息。 #### 利用 ExecutorService 进行更高效的并发控制 对于更加复杂的场景或是希望更好地管理和调度多个工作单元的应用程序来说,则推荐使用更高层次抽象——即基于 `ExecutorService` API 构建的服务端框架来代替手动管理线程生命周期的做法。特别是当涉及到大量短命轻量级作业的时候,这种方式能显著提高性能表现以及简化编码逻辑[^4]。 这里给出一段示范性的片段说明如何借助此类工具集达成目的: ```java import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; class TaskWithReturn implements Callable<Integer> { private int id; public TaskWithReturn(int id){ this.id=id; } @Override public Integer call(){ // 假设这里是某些复杂运算过程... return compute(id); } private int compute(int n){ // 简单模拟一些数值变换作为示例用途 return n * n; } } // ... ExecutorService executor = Executors.newFixedThreadPool(5); List<Future<Integer>> futures = new ArrayList<>(); for (int i=0;i<10;++i){ futures.add(executor.submit(new TaskWithReturn(i))); } try{ for(Future<Integer> f :futures){ System.out.printf("Task %d returned value:%d%n",futures.indexOf(f)+1,f.get()); } }finally{ executor.shutdown(); // 记得关闭资源释放连接池 } ``` 上述案例中,先初始化了一个固定大小为五的工作队列,接着连续提交十个不同的任务进去排队等候被执行。最后遍历所有已提交项收集它们各自的成果报告出来显示给用户查看。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值