Java併發(具體實例)——幾個例子

一步步優化頁面渲染功能                                                          java

      本節將模擬一個簡單的頁面渲染功能,它的做用是將HTML頁面繪製到圖像緩存中,爲了簡便,假設HTML文件只包含標籤文本以及預訂大小的圖片和URL。編程

一、串行的頁面渲染器緩存

      最簡單的實現方式是對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利用率和響應靈敏度。併發

二、使用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());
        }
    }
}

  固然,咱們還能夠優化,用戶其實不須要等待全部圖像下載完成,咱們能夠每下載完一張圖像就馬上顯示出來。優化

三、使用CompletionService實現頁面渲染器網站

      要實現下載完一張就馬上繪製,咱們須要及時知道圖片下載完成,對於這種場景,CompletionService十分符合需求。CompletionService將生產新的異步任務與使用已完成任務的結果分離開來的服務。生產者 submit 執行的任務,使用者 take 已完成的任務,並按照完成這些任務的順序處理它們的結果。下面的代碼使用CompletionService改寫了頁面渲染器的實現:this

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併發編程實戰》

相關文章
相關標籤/搜索