zoukankan      html  css  js  c++  java
  • Java并发(具体实例)——几个例子

    一步步优化页面渲染功能                                                          

          本节将模拟一个简单的页面渲染功能,它的作用是将HTML页面绘制到图像缓存中,为了简便,假设HTML文件只包含标签文本以及预订大小的图片和URL。

    1、串行的页面渲染器

          最简单的实现方式是对HTML文档进行串行处理:先绘制文本,然后绘制图像,串行处理:

    public class SingleThreadRenderer {
        void renderPage(CharSequence source) {
            renderText(source);
            List<ImageData> imageData = new ArrayList<ImageData>();
            for (ImageInfo imageInfo : scanForImageInfo(source))
                imageData.add(imageInfo.downloadImage());
            for (ImageData data : imageData)
                renderImage(data);
        }
    }
    

      这种实现方式有个问题,因为图像下载过程的大部分时间都是在等待I/O操作执行完成,在这期间CPU几乎不做任何工作。因此,这种执行方式没有充分地利用CPU,使得用户在看到最终页面之前要等待过长时间。通过将问题分解为多个独立的任务并发执行,能够活得更高的CPU利用率和响应灵敏度。

    2、使用Future实现页面渲染器

          为了使页面渲染器实现更高的并发性,首先将渲染过程分解为两个任务,一个是渲染所有的文本,另一个是下载所有的图像(一个是CPU密集型,一个是I/O密集型)。Callable和Future有助于表示这种协同任务的交互,以下代码首先创建一个Callable来下载所有的图像,当主任务需要图像时,它会等待Future.get的调用结果。如果幸运的话,图像可能已经下载完成,即使没有,至少也已经提前开始下载。

    public class FutureRenderer {
        private final ExecutorService executor = Executors.newCachedThreadPool();
    
        void renderPage(CharSequence source) {
            final List<ImageInfo> imageInfos = scanForImageInfo(source);
            Callable<List<ImageData>> task =
                    new Callable<List<ImageData>>() {
                        public List<ImageData> call() {
                            List<ImageData> result = new ArrayList<ImageData>();
                            for (ImageInfo imageInfo : imageInfos)
                                result.add(imageInfo.downloadImage());
                            return result;
                        }
                    };
    
            Future<List<ImageData>> future = executor.submit(task);
            renderText(source);
    
            try {
                List<ImageData> imageData = future.get();
                for (ImageData data : imageData)
                    renderImage(data);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                future.cancel(true);
            } catch (ExecutionException e) {
                throw launderThrowable(e.getCause());
            }
        }
    }
    

      当然,我们还可以优化,用户其实不需要等待所有图像下载完成,我们可以每下载完一张图像就立刻显示出来。

    3、使用CompletionService实现页面渲染器

          要实现下载完一张就立刻绘制,我们需要及时知道图片下载完成,对于这种场景,CompletionService十分符合需求。CompletionService将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务,使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。下面的代码使用CompletionService改写了页面渲染器的实现:

    public abstract class Renderer {
        private final ExecutorService executor;
    
        Renderer(ExecutorService executor) {
            this.executor = executor;
        }
    
        void renderPage(CharSequence source) {
            final List<ImageInfo> info = scanForImageInfo(source);
            CompletionService<ImageData> completionService =
                    new ExecutorCompletionService<ImageData>(executor);
            for (final ImageInfo imageInfo : info)
                completionService.submit(new Callable<ImageData>() {
                    public ImageData call() {
                        return imageInfo.downloadImage();
                    }
                });
    
            renderText(source);
    
            try {
                for (int t = 0, n = info.size(); t < n; t++) {
                    Future<ImageData> f = completionService.take();
                    ImageData imageData = f.get();
                    renderImage(imageData);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (ExecutionException e) {
                throw launderThrowable(e.getCause());
            }
        }
    }
    

    为任务设置时限                                                                       

          有时候,如果某个任务无法在指定时间内完成,那么将不再需要它的结果,此时可以放弃这个任务。例如,某个Web应用程序从外部的广告服务器上获取广告信息,但是如果该应用程序在两秒内得不到响应,那么将显示一个默认的广告页,这样即使不能活得广告信息,也不会降低站点的响应性能,对于这种需求,Future.get方法可以实现:

        Page renderPageWithAd() throws InterruptedException {
            long endNanos = System.nanoTime() + TIME_BUDGET;
            Future<Ad> f = exec.submit(new FetchAdTask());
            // Render the page while waiting for the ad
            Page page = renderPageBody();
            Ad ad;
            try {
                // Only wait for the remaining time budget
                long timeLeft = endNanos - System.nanoTime();
                ad = f.get(timeLeft, NANOSECONDS);
            } catch (ExecutionException e) {
                ad = DEFAULT_AD;
            } catch (TimeoutException e) {
                ad = DEFAULT_AD;
                f.cancel(true);
            }
            page.setAd(ad);
            return page;
        }
    

      这种"预订时间"的方法可以很容易地扩展到任意数量的任务上,考虑这样一个旅行网站:用户输入旅行日期及要求,网站通过多种途径获取结果,此时,不应该让页面的响应时间受限于最慢的途径,而应该只显示在指定时间内收到的消息,我们可以通过使用支持限时的invokeAll,将多个任务提交到一个ExecutorService的方式实现这个需求:

    public class TimeBudget {
        private static ExecutorService exec = Executors.newCachedThreadPool();
    
        public List<TravelQuote> getRankedTravelQuotes(TravelInfo travelInfo, Set<TravelCompany> companies,
                                                       Comparator<TravelQuote> ranking, long time, TimeUnit unit)
                throws InterruptedException {
            List<QuoteTask> tasks = new ArrayList<QuoteTask>();
            for (TravelCompany company : companies)
                tasks.add(new QuoteTask(company, travelInfo));
    
            List<Future<TravelQuote>> futures = exec.invokeAll(tasks, time, unit);
    
            List<TravelQuote> quotes =
                    new ArrayList<TravelQuote>(tasks.size());
            Iterator<QuoteTask> taskIter = tasks.iterator();
            for (Future<TravelQuote> f : futures) {
                QuoteTask task = taskIter.next();
                try {
                    quotes.add(f.get());
                } catch (ExecutionException e) {
                    quotes.add(task.getFailureQuote(e.getCause()));
                } catch (CancellationException e) {
                    quotes.add(task.getTimeoutQuote(e));
                }
            }
    
            Collections.sort(quotes, ranking);
            return quotes;
        }
    
    }
    
    class QuoteTask implements Callable<TravelQuote> {
        private final TravelCompany company;
        private final TravelInfo travelInfo;
    
        public QuoteTask(TravelCompany company, TravelInfo travelInfo) {
            this.company = company;
            this.travelInfo = travelInfo;
        }
    
        TravelQuote getFailureQuote(Throwable t) {
            return null;
        }
    
        TravelQuote getTimeoutQuote(CancellationException e) {
            return null;
        }
    
        public TravelQuote call() throws Exception {
            return company.solicitQuote(travelInfo);
        }
    }
    
    interface TravelCompany {
        TravelQuote solicitQuote(TravelInfo travelInfo) throws Exception;
    }
    
    interface TravelQuote {
    }
    
    interface TravelInfo {
    }
    

          例子来自:《Java并发编程实战》

      

  • 相关阅读:
    改造vant日期选择
    css3元素垂直居中
    npm综合
    (转)网页加水印方法
    Mac下IDEA自带MAVEN插件的全局环境配置
    隐藏注册控件窗口
    High performance optimization and acceleration for randomWalk, deepwalk, node2vec (Python)
    How to add conda env into jupyter notebook installed by pip
    The Power of WordNet and How to Use It in Python
    背单词app测评,2018年
  • 原文地址:https://www.cnblogs.com/timlearn/p/4125111.html
Copyright © 2011-2022 走看看