• 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并发编程实战》

      

  • 相关阅读:
    Java学习笔记-函数
    Java学习笔记-数组
    Git 常用命令速查表
    $.fn与$.fx什么意思; $.extend与$.fn.extend用法区别; $(function(){})和(function(){})(jQuery)
    offsetWidth的bug
    jQuery对象和DOM对象转换,解决jQuery对象不能使用js方法的问题
    1
    $().ready()与window.onload的不同
    offsetHeight在不同的浏览器下取值不同
    getElementsByName兼容ie 但并不是兼容ie下的所有标签
  • 原文地址:https://www.cnblogs.com/timlearn/p/4125111.html
Copyright © 2020-2023  润新知