如何实现线程返回值——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 {
    //第几分段
    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 ft1 = new FutureTask<>(new SegmentTask(1,n,p));
        FutureTask ft2 = new FutureTask<>(new SegmentTask(2,n,p));
        FutureTask ft3 = new FutureTask<>(new SegmentTask(3,n,p));
        FutureTask 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_第1张图片
FutureTask实现了Runnable和Future接口,其中实现Runnable接口可创建线程(常见用法:交给Thread或Executor执行):

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

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

public interface Future {

    /**
     * 尝试取消任务,如果该任务已经完成、已经取消或者因为其他原因不能取消的话就会执行失败。执行成功的话,
     * 如果该任务还没开始,那这个任务就不会跑起来;如果该任务已经在跑了,那么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 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子线程执行,后返回结果给主线程。

你可能感兴趣的:(知识笔记,多线程)