简单聊聊线程执行完成后如何获取结果(Callable和Future)

线程大家都很熟悉,一般在做耗时操作的时候,我们会开启一个线程来帮我们完成耗时任务,这样就可以避免主线程阻塞,提高用户体验。

但是在使用线程的时候有一个问题,线程本身执行情况我们是无法获取的。一般我们会加一个接口之类的在线程完成后回调。其实这个问题我们也可以使用Callable和Future来解决。


Callable

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

以上是Callable的源码,可以看到Callable是一个接口,里面只有一个方法call(),并且带有返回参数。我们从这个类的解释可以看出来,它是一个可以返回结果或者抛出异常的任务,解决了Runnable没有返回值等问题,可以结合Executors一起来使用。

在运用时我们一般会写一个Callable的实现类,然后再call()方法中完成耗时任务。

    private static class TestCallAble implements Callable {

        @Override
        public String call() throws Exception {
            Thread.sleep(3000);
            return "finish";
        }
    }

接下来我们看Executors中执行Callable中的耗时任务的方法。

<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);

我们先来看第一个方法,这里传入Callable,我们一直跟到源码,最终我们发现执行了这个方法。

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

方法中先是做了一个null的判断,然后调用newTaskFor(),这里实际上是返回了一个FutureTask类,FutureTask实现了RunnableFuture接口。而RunnableFuture继承了Runnable和Future。最后再调用execute()方法来异步执行任务。

接着我们看第二个方法

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

我们看到第二个方法传入了一个Runnable和一个返回值result。进入submit方法后我们发现这里和传入callable时调用的方法基本一样,只在在newTaskFor()的时候多传入了返回值result。我们继续跟进去,最后在FutureTask类中我们发现在初始化FutureTask的时候调用了Executors.callable(task, result)方法。

    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

我们进入Executors并找到源码,我们发现返回值是RunnableAdapter。RunnableAdapter是实现了Callable,在call()方法中执行了Runnable的run方法,并在最后返回了传入的返回值result。讲到这里大家就明白了这里只是把传入的Runnable经过一系列转换变成了实现Callable接口的RunnableAdapter类。

第三个方法和第二个方法差不多,只是第三个方法返回值为null,具体大家可以自己参看源码,这里就不分析了。


Future

public interface Future<V>

我们可以看到Future是一个接口,上面方法中也都返回了参数Future。

boolean cancel(boolean mayInterruptIfRunning);

boolean isCancelled();

boolean isDone();

V get() throws InterruptedException, ExecutionException;

V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

接口中有这5个方法:
cancel(boolean mayInterruptIfRunning) 是取消任务,方法中需要传入一个布尔值。意思是当任务正在执行的时候是否取消,如果是true,那么任务就算是执行中也会取消。是否取消成功会有一个返回值。

isCancelled(),isDone() 相信大家看名字就可以知道作用。

get() 是获取返回值,需要注意的是这个方法会等到任务执行完成后才会回调返回值,然后继续执行下面的代码。

get(long timeout, TimeUnit unit) 传入了时间,这里是在限制时间内获取返回值,如果传入的时间到了任务还没有执行完成,会抛出异常。

执行完submit方法后实际返回的是FutureTask类,FutureTask中实现了接口Future的所有方法。所以我们在调用方法的时候是执行FutureTask中的方法。

方法的具体实现这里不做分析,我就简单说说get()的原理,在FutureTask类中get()其实就是一个for循环,一直在循环等待线程中的任务完成,任务完成后,在将外部传入的返回值(result)返回出去。肯定有人会有疑问这样难道就不阻塞线程吗?如果没有其他操作这样确实会阻塞线程,但是在源码中我们可以看到有这样的代码:

else if (s == COMPLETING) // cannot time out yet
Thread.yield();

这样大家就明白了为什么它一直循环等待也不会有阻塞问题了吧。


实际运用:

public class Main {

    public static void main(String[] args) {

        //执行方法1
        ExecutorService pool = Executors.newCachedThreadPool();
        Future future = pool.submit(new TestCallAble());
        try {
            System.out.println("result = " + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        //执行方法2
        FutureTask<String> task = new FutureTask<String>(new TestCallAble());
        Thread thread = new Thread(task);
        thread.start();

        try {
            System.out.println("result = " + task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static class TestCallAble implements Callable {

        @Override
        public String call() throws Exception {
            Thread.sleep(3000);
            return "finish";
        }
    }

}

这个例子很简单,就是在call()中延时3秒来模拟任务,然后分别用两个方法来执行这个任务,得出的结果是一样的(实际运用中建议用方法1来执行任务)。


如果上文中有错误的地方希望大家指出,我好及时修正,其它问题也可以和我交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • Android Handler机制系列文章整体内容如下: Android Handler机制1之ThreadAnd...
    隔壁老李头阅读 4,255评论 2 12
  •   一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺...
    OmaiMoon阅读 1,671评论 0 12
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,106评论 0 23
  • 当我们创建一个线程时,我们想获取线程运行完成后的结果,一般使用回调的方式。例如: 运行结果: 这种方式的实现有三个...
    wo883721阅读 5,712评论 2 9
  • 今天偶然点进姐姐的微博,让我心里有一种震撼和沉思,一直默默努力的姐姐的有坚定的目标和远大的理想,同时最让我钦佩的...
    栀子Vivian阅读 133评论 0 1