美文网首页
简单聊聊线程执行完成后如何获取结果(Callable和Futur

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

作者: Jevely | 来源:发表于2019-02-16 16:41 被阅读40次

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

但是在使用线程的时候有一个问题,线程本身执行情况我们是无法获取的。一般我们会加一个接口之类的在线程完成后回调。其实这个问题我们也可以使用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来执行任务)。


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

相关文章

网友评论

      本文标题:简单聊聊线程执行完成后如何获取结果(Callable和Futur

      本文链接:https://www.haomeiwen.com/subject/xsseeqtx.html