美文网首页
简单聊聊线程执行完成后如何获取结果(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