Java并发使用场景——几个例子

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

1. 使用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());
        }
    }
}

 2. 使用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并发编程实战》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值