Book excerpt: Executing tasks in threads

When creating threads to perform tasks, look to the Executor framework

1 2 3 4 Page 4
Page 4 of 4
                        

public 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()); } } }

Multiple ExecutorCompletionServices can share a single Executor, so it is perfectly sensible to create an ExecutorCompletionService that is private to a particular computation while sharing a common Executor. When used in this way, a CompletionService acts as a handle for a batch of computations in much the same way that a Future acts as a handle for a single computation. By remembering how many tasks were submitted to the CompletionService and counting how many completed results are retrieved, you can know when all the results for a given batch have been retrieved, even if you use a shared Executor.

Placing time limits on tasks

Sometimes, if an activity does not complete within a certain amount of time, the result is no longer needed and the activity can be abandoned. For example, a Web application may fetch its advertisements from an external ad server, but if the ad is not available within two seconds, it instead displays a default advertisement so that ad unavailability does not undermine the site's responsiveness requirements. Similarly, a portal site may fetch data in parallel from multiple data sources, but may be willing to wait only a certain amount of time for data to be available before rendering the page without it.

The primary challenge in executing tasks within a time budget is making sure that you don't wait longer than the time budget to get an answer or find out that one is not forthcoming. The timed version of Future.get supports this requirement: it returns as soon as the result is ready, but throws TimeoutException if the result is not ready within the timeout period.

A secondary problem when using timed tasks is to stop them when they run out of time, so they do not waste computing resources by continuing to compute a result that will not be used. This can be accomplished by having the task strictly manage its own time budget and abort if it runs out of time, or by cancelling the task if the timeout expires. Again, Future can help; if a timed get completes with a TimeoutException, you can cancel the task through the Future. If the task is written to be cancellable, it can be terminated early so as not to consume excessive resources. This technique is used in Listings 13 and 16.

Listing 16 shows a typical application of a timed Future.get. It generates a composite Webpage that contains the requested content plus an advertisement fetched from an ad server. It submits the ad-fetching task to an executor, computes the rest of the page content, and then waits for the ad until its time budget runs out. (The timeout passed to get is computed by subtracting the current time from the deadline; this may in fact yield a negative number, but all the timed methods in java.util.concurrent treat negative timeouts as zero, so no extra code is needed to deal with this case.) If the get times out, it cancels the ad-fetching task and uses a default advertisement instead. (The true parameter to Future.cancel means that the task thread can be interrupted if the task is currently running.)

Listing 16. Fetching an advertisement with a time budget

                        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;
}
                   

Example: A travel reservations portal

The time-budgeting approach in the previous section can be easily generalized to an arbitrary number of tasks. Consider a travel reservation portal: the user enters travel dates and requirements and the portal fetches and displays bids from a number of airlines, hotels or car rental companies. Depending on the company, fetching a bid might involve invoking a Web service, consulting a database, performing an EDI transaction, or some other mechanism. Rather than have the response time for the page be driven by the slowest response, it may be preferable to present only the information available within a given time budget. For providers that do not respond in time, the page could either omit them completely or display a placeholder such as "Did not hear from Air Java in time."

Fetching a bid from one company is independent of fetching bids from another, so fetching a single bid is a sensible task boundary that allows bid retrieval to proceed concurrently. It would be easy enough to create n tasks, submit them to a thread pool, retain the Futures, and use a timed get to fetch each result sequentially via its Future, but there is an even easierway�invokeAll.

Listing 17 uses the timed version of invokeAll to submit multiple tasks to an ExecutorService and retrieve the results. The invokeAll method takes a collection of tasks and returns a collection of Futures. The two collections have identical structures; invokeAll adds the Futures to the returned collection in the order imposed by the task collection's iterator, thus allowing the caller to associate a Future with the Callable it represents. The timed version of invokeAll will return when all the tasks have completed, the calling thread is interrupted, or the timeout expires. Any tasks that are not complete when the timeout expires are cancelled. On return from invokeAll, each task will have either completed normally or been cancelled; the client code can call get or isCancelled to find out which.

Listing 17. Requesting travel quotes under a time budget

                        

private class QuoteTask implements Callable<TravelQuote> { private final TravelCompany company; private final TravelInfo travelInfo; ... public TravelQuote call() throws Exception { return company.solicitQuote(travelInfo); } 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; }

Summary

Structuring applications around the execution of tasks can simplify development and facilitate concurrency. The Executor framework permits you to decouple task submission from execution policy and supports a rich variety of execution policies; whenever you find yourself creating threads to perform tasks, consider using an Executor instead. To maximize the benefit of decomposing an application into tasks, you must identify sensible task boundaries. In some applications, the obvious task boundaries work well, whereas in others some analysis may be required to uncover finer-grained exploitable parallelism.

Brian Goetz is a software consultant with twenty years of industry experience and more than 75 articles on Java development. He is one of the primary members of the Java Community Process JSR 166 Expert Group (Concurrency Utilities) and has served on numerous other JCP expert groups. Tim Peierls is the very model of a modern multiprocessor, with BoxPop.biz, recording arts, and goings on theatrical. He is one of the primary members of the Java Community Process JSR 166 Expert Group (Concurrency Utilities), and has served on numerous other JCP expert groups. Joshua Bloch is a principal engineer at Google. Previously he was a distinguished engineer at Sun Microsystems and a senior systems designer at Transarc. He led the design and implementation of numerous Java platform features, including the Java 5.0 language enhancements and the Java Collections Framework. He holds a Ph.D. in computer science from Carnegie-Mellon University and a B.S. in computer science from Columbia University. Joseph Bowbeer is a software architect at Vizrea Corporation, where he specializes in mobile application development for the Java ME platform, but his fascination with concurrent programming began in his days at Apollo Computer. He served on the JCP expert group for JSR-166 (Concurrency Utilities). David Holmes is a senior research scientist at the Cooperative Research Centre for Enterprise Distributed Systems Technology (DSTC Pty Ltd), located in Brisbane, Australia. His work with Java technology has focused on concurrency and synchronization support in the language and virtual machine. He has presented tutorials on concurrency and design at numerous international object-oriented programming conferences. He completed his Ph.D. at Macquarie University, Sydney, in 1999. Doug Lea is one of the foremost experts on object-oriented technology and software reuse. He has been doing collaborative research with Sun Labs for more than five years. Lea is professor of computer science at SUNY Oswego, co-director of the Software Engineering Lab at the New York Center for Advanced Technology in Computer Applications, and adjunct professor of electrical and computer engineering at Syracuse University. In addition, he co-authored the book, Object-Oriented System Development (Addison-Wesley, 1993). He received his B.A., M.A., and Ph.D. from the University of New Hampshire.

Learn more about this topic

Related:
1 2 3 4 Page 4
Page 4 of 4