• 携带结果的任务 Callable 与 Future


    Executor框架使用Runnable作为其基本任务表示形式。Runnable是一种有很大局限的抽象,它不能返回一个值或者抛出一个受检查的异常。

    但是许多任务实际上都是存在延迟的计算,比如执行数据库查询,从网络上获取资源,或者计算某个复杂的功能。对于这些任务,就要Callable来显身手了。

    public interface Callable<V>{
        V call() throws Exception;
    }

    Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务等。

    public interface Future<V> {
    
        boolean cancel(boolean mayInterruptIfRunning);
    
        boolean isCancelled();
    
        boolean isDone();
    
        V get() throws InterruptedException, ExecutionException;
    
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

    ExecutorService中所有submit方法都将返回一个Future,从而将一个Runnable或Callable提交给Executor,并得到一个Future来获得任务执行结果或者取消任务。

    示例:使用Future实现页面渲染器

    public class FutureRenderer {
    
        private final ExecutorService executorService = Executors.newFixedThreadPool(5);
    
        public void renderPage(CharSequence source) {
            final List<ImageInfo> imageInfoList = scanForImageInfo(source);
            Callable<List<ImageData>> task = new Callable<List<ImageData>>() {
                public List<ImageData> call() throws Exception {
                    List<ImageData> result = new ArrayList<ImageData>();
                    for (ImageInfo imageInfo:imageInfoList)
                        result.add(imageInfo.downloadImage());
                    return null;
                }
            };
            Future<List<ImageData>> future = executorService.submit(task);
            renderText(source);
            try {
                List<ImageData> imageDatas = future.get();
                for (ImageData data : imageDatas) {
                    renderImage(data);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                future.cancel(true);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
        }
    }

    get方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。如果任务已经完成,那么get会立即返回或者抛出一个Exception,如果任务没有完成,那么get将阻塞并直到任务完成。

    还可以显式地为某个指定的Runnable或Callable实例化一个FutureTask(由于FutureTask实现了Runnable,因此可以将它提交给Executor来执行或者直接调用它的run方法)。

     ExecutorService executorService = Executors.newFixedThreadPool(5);
     FutureTask futureTask = new FutureTask(new Callable() {
         @Override
         public Object call() throws Exception {
             return null;
         }
    });
    executorService.submit(futureTask);
    //futureTask.run();

    CompletionService

    CompletionService将Executor和BlockingQueue的功能融合在一起。你可以将Callable任务提交给它来执行,然后使用类似于队列操作的take和poll等方法来获得已完成的结果。

    示例:使用CompletionService实现页面渲染器

    public class Renderer {
    
        private final ExecutorService executorService;
    
        public Renderer(ExecutorService executorService) {
            this.executorService = executorService;
        }
    
        public void renderPage(CharSequence source) {
            final List<ImageInfo> imageInfoList = scanForImageInfo(source);
            CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(executorService);
            for (final ImageInfo imageInfo : imageInfoList) {
                completionService.submit(new Callable<ImageData>() {
                    public ImageData call() throws Exception {
                        return imageInfo.downloadImage();
                    }
                });
            }
    
            renderText(source);
    
            try {
                for (int i = 0, n = imageInfoList.size(); i < n; i++) {
                    Future<ImageData> f = completionService.take();
                    ImageData imageData = f.get();
                    renderImage(data);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                future.cancel(true);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
        }
    }

    以上的代码从两个方面提高了页面渲染器的性能:

    1. 为每一幅图像的下载都创建一个独立任务,并在线程池中执行它们,从而将串行的下载过程转换为并行的过程:这将减少下载所有图像的总时间。
    2. 通过从CompletionService中获取结果以及使每张图片下载完成后立刻显示出来,能使用户获得一个更加动态和更加响应性的用户界面。

    可以看摘取的部分源码:

    public class ExecutorCompletionService<V> implements CompletionService<V> {
        private final Executor executor;
        private final AbstractExecutorService aes;
        private final BlockingQueue<Future<V>> completionQueue;
    
        /**
         * FutureTask extension to enqueue upon completion
         */
        private class QueueingFuture extends FutureTask<Void> {
            QueueingFuture(RunnableFuture<V> task) {
                super(task, null);
                this.task = task;
            }
            protected void done() { completionQueue.add(task); }
            private final Future<V> task;
        }
    
        public ExecutorCompletionService(Executor executor) {
            if (executor == null)
                throw new NullPointerException();
            this.executor = executor;
            this.aes = (executor instanceof AbstractExecutorService) ?
                (AbstractExecutorService) executor : null;
            this.completionQueue = new LinkedBlockingQueue<Future<V>>();
        }
    
        public Future<V> submit(Callable<V> task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<V> f = newTaskFor(task);
            executor.execute(new QueueingFuture(f));
            return f;
        }
    
        public Future<V> submit(Runnable task, V result) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<V> f = newTaskFor(task, result);
            executor.execute(new QueueingFuture(f));
            return f;
        }
    
        //...
    }

    ExecutorCompletionService实现了CompletionService,并将计算部分委托给一个Executor。在构造函数中创建一个BlockingQueue来保存计算完成的结果。

    Callable和Future总是如影随形,通过一个submit方法连接起来,使任务携带结果并随时取出结果成为可能。

    ================================== 赵客缦胡缨,吴钩霜雪明。 银鞍照白马,飒沓如流星。 ==================================
  • 相关阅读:
    Python+Selenium三种等待方法
    Jmeter结果分析_聚合报告
    Linux安装Python3
    翻译Go Blog: 常量
    Go: 复合数据类型slice
    Python创建二维列表的正确姿势
    了解Flask
    urllib3中学到的LRU算法
    了解Prometheus
    《redis 5设计与源码分析》:第二章 简单动态字符串
  • 原文地址:https://www.cnblogs.com/lucare/p/9312660.html
Copyright © 2020-2023  润新知