`

Finding Exploitable Parallelism

阅读更多
6.3. Finding Exploitable Parallelism
The Executor framework makes it easy to specify an execution policy, but in order to use an Executor, you have to be able to describe your task as a Runnable. In most server applications, there is an obvious task boundary: a single client request. But sometimes good task boundaries are not quite so obvious, as in many desktop applications. There may also be exploitable parallelism within a single client request in server applications, as is sometimes the case in database servers. (For a further discussion of the competing design forces in choosing task boundaries, see [CPJ 4.4.1.1].)
Executor框架使得制定执行政策很容易,但是为了使用Executor,你必须使用Runnable来描述任务。在大多数服务器应用程序中,有一个明显的任务限制:单一的客户端请求。但是有些时候一个好的任务限制并不是非常明显,就像许多桌面应用程序一样。也有可能在服务端针对一个客户端进行并行开发的,比如数据库服务器。

Listing 6.9. Class Illustrating Confusing Timer Behavior.

public class OutOfTime {
    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.schedule(new ThrowTask(), 1);
        SECONDS.sleep(1);
        timer.schedule(new ThrowTask(), 1);
        SECONDS.sleep(5);
    }

    static class ThrowTask extends TimerTask {
        public void run() { throw new RuntimeException(); }
    }
}

In this section we develop several versions of a component that admit varying degrees of concurrency. Our sample component is the page-rendering portion of a browser application, which takes a page of HTML and renders it into an image buffer. To keep it simple, we assume that the HTML consists only of marked up text interspersed with image elements with pre-specified dimensions and URLs.
在本节中我们开发不同版本的组件,它容许不同程度的并发。我们以浏览器的页面渲染部分为例,这需要一个HTML页面和渲染图像的缓冲区。为了简单起见,我们假定的HTML包含标记的文本,只有预先指定的尺寸的图像和URL元素穿插

6.3.1. Example: Sequential Page Renderer
The simplest approach is to process the HTML document sequentially. As text markup is encountered, render it into the image buffer; as image references are encountered, fetch the image over the network and draw it into the image buffer as well. This is easy to implement and requires touching each element of the input only once (it doesn't even require buffering the document), but is likely to annoy the user, who may have to wait a long time before all the text is rendered.
最简单的办法就是顺序地处理HTML文档。当遇到文本标记,将它渲染进图像缓冲区;当遇到图像引用,通过网络获取图像,并将它放到图像缓冲区中。这样很容易实现,而且需要触摸输入的每个元素只有一次(它甚至不需要缓冲文件),但很可能惹恼用户,因为他们可能要等待很长一段时间,才能看到所有的文字呈现。

A less annoying but still sequential approach involves rendering the text elements first, leaving rectangular placeholders for the images, and after completing the initial pass on the document, going back and downloading the images and drawing them into the associated placeholder. This approach is shown in SingleThreadRenderer in Listing 6.10.
一种不是那么恼人的做法也是顺序执行,先渲染文本,使用占位符为图像占位,然后下载图片。如同SingleThreadRenderer在例子6.10中所示。

Downloading an image mostly involves waiting for I/O to complete, and during this time the CPU does little work. So the sequential approach may underutilize the CPU, and also makes the user wait longer than necessary to see the finished page. We can achieve better utilization and responsiveness by breaking the problem into independent tasks that can execute concurrently.
下载图片一般需要等待输入/输出的完成,在等待的这段时间里,CPU什么事也不干。所以顺序执行时CPU的利用率很低,并且需要用户等待很长时间才能看到整个页面。通过将问题分解成独立的任务来并行执行,我们可以获得更好的CPU利用率和更快的响应速度。


Listing 6.10. Rendering Page Elements Sequentially.

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





6.3.2. Result-bearing Tasks: Callable and Future
The Executor framework uses Runnable as its basic task representation. Runnable is a fairly limiting abstraction; run cannot return a value or throw checked exceptions, although it can have side effects such as writing to a log file or placing a result in a shared data structure.
Executor使用Runnable作为任务的表达方式。Runnable接口是一个有局限性的抽象;run方法不能返回一个值或抛出checkedexceptions,尽管它通过其他的方式来实现,如写入到日志文件中或放置在一个共享的数据结构的结果。

Many tasks are effectively deferred computations executing a database query, fetching a resource over the network, or computing a complicated function. For these types of tasks, Callable is a better abstraction: it expects that the main entry point, call, will return a value and anticipates that it might throw an exception.[7] Executors includes several utility methods for wrapping other types of tasks, including Runnable and java.security.PrivilegedAction, with a Callable.
许多任务有效地延迟计算,如执行数据库查询,获取网络资源,或者一个复杂功能的计算。对于这些任务,Callable是一个更好的抽象:它期望的主要入口点就是Call方法,Call将返回一个值或者抛出一个异常。Executors包含一些使用方法通过Callable来封装其他类型的任务,如Runnable和java.security.PrivilegedAction。

[7] To express a non-value-returning task with Callable, use Callable<Void>.
使用Callable<Void>可以来表示没有值返回的任务。

Runnable and Callable describe abstract computational tasks. Tasks are usually finite: they have a clear starting point and they eventually terminate. The lifecycle of a task executed by an Executor has four phases: created, submitted, started, and completed. Since tasks can take a long time to run, we also want to be able to cancel a task. In the Executor framework, tasks that have been submitted but not yet started can always be cancelled, and tasks that have started can sometimes be cancelled if they are responsive to interruption. Cancelling a task that has already completed has no effect. (Cancellation is covered in greater detail in Chapter 7.)
Runnable和Callable可以来描述抽象的计算任务。任务往往是有限制的:有明确的起点,并且最后会终结。由Executor执行的任务的生命周期有四个阶段:创建、提交、开始执行和结束。因为任务可能会执行很长时间,所以我们希望能取消一个任务。在Executor框架中,一个被提交的任务如果还没有被执行是可以被取消的,如果任务已经执行了并处于响应中断状态的,也可以被取消。取消已经执行完成的任务是没有作用的。(第7章会讨论取消任务的细节)


Future represents the lifecycle of a task and provides methods to test whether the task has completed or been cancelled, retrieve its result, and cancel the task. Callable and Future are shown in Listing 6.11. Implicit in the specification of Future is that task lifecycle can only move forwards, not backwards just like the ExecutorService lifecycle. Once a task is completed, it stays in that state forever.
Future类表述了一个任务的生命周期,并且提供了判断一个任务有没有结束或取消的方法,可以检索结果和取消任务。Callable和Future如6.11中所示。Future隐含的说明了,任务的生命周期只能向前,不能像ExecutorService的生命周期那样后退。一旦一个任务结束了,他就永远处于那个状态。

The behavior of get varies depending on the task state (not yet started, running, completed). It returns immediately or throws an Exception if the task has already completed, but if not it blocks until the task completes. If the task completes by throwing an exception, get rethrows it wrapped in an ExecutionException; if it was cancelled, get throws CancellationException. If get throws ExecutionException, the underlying exception can be retrieved with getCause.
Future类中get方法的结果由于任务的状态(未开始、执行、结束)的不同而不同。如果任务已经结束,它会很快返回结果或者抛出异常,如果没有阻塞的话。如果任务通过抛异常而结束,get方法将这个异常封装在ExecutionException中,重新抛出;如果任务被取消,get方法抛出CancellationException;如果get方法抛出ExecutionException,根本的异常可以查询到。

Listing 6.11. Callable and Future Interfaces.
public interface Callable<V> {
    V call() throws Exception;
}

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException,
                   CancellationException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException,
               CancellationException, TimeoutException;
}

There are several ways to create a Future to describe a task. The submit methods in ExecutorService all return a Future, so that you can submit a Runnable or a Callable to an executor and get back a Future that can be used to retrieve the result or cancel the task. You can also explicitly instantiate a FutureTask for a given Runnable or Callable. (Because FutureTask implements Runnable, it can be submitted to an Executor for execution or executed directly by calling its run method.)
通过Future来表示任务有几种方法。ExecutorService类的submit方法都是返回一个Future,因此你可以提交一个Runnable或者Callable给一个executor,从而获得一个Future,可以用来查询结果或者取消任务。你也可以显式地用Runnable或者Callable来初始化一个FutureTask.(因为FutureTask实现了Runnable,它可以被提交给一个Executor来执行,或者直接调用它的run方法来执行)。

As of Java 6, ExecutorService implementations can override newTaskFor in AbstractExecutorService to control instantiation of the Future corresponding to a submitted Callable or Runnable. The default implementation just creates a new FutureTask, as shown in Listing 6.12.
在JAVA6中,ExecutorService的实现类可以重写AbstractExecutorService类中的newTaskFor方法来空值Future的实例化,这个和提交Callable、Runnable是一致的。如6.12所示。

Listing 6.12. Default Implementation of newTaskFor in ThreadPoolExecutor.
protected <T> RunnableFuture<T> newTaskFor(Callable<T> task) {
    return new FutureTask<T>(task);
}

Submitting a Runnable or Callable to an Executor constitutes a safe publication (see Section 3.5) of the Runnable or Callable from the submitting thread to the thread that will eventually execute the task. Similarly, setting the result value for a Future constitutes a safe publication of the result from the thread in which it was computed to any thread that retrieves it via get.
提交一个Runnable或Callable给一个Executor,由提交线程给最终执行任务的线程组成,是一个安全的Runnable和Callable的发布。类似地,为Future设置返回值,由计算的线程到通过get方法查询的线程组成,是结果的安全发布。

6.3.3. Example: Page Renderer with Future
As a first step towards making the page renderer more concurrent, let's divide it into two tasks, one that renders the text and one that downloads all the images. (Because one task is largely CPU-bound and the other is largely I/O-bound, this approach may yield improvements even on single-CPU systems.)
第一步就是使得页面渲染有更多的并发实现,我们把它分成两个任务,一个进行文本渲染,另一个下载所有的图片(因为一个任务是高cpu占用率,另一个任务那是大量的I/O占用,这种方法可能产生改进,即使是在单CPU系统中)。

Callable and Future can help us express the interaction between these cooperating tasks. In FutureRenderer in Listing 6.13, we create a Callable to download all the images, and submit it to an ExecutorService. This returns a Future describing the task's execution; when the main task gets to the point where it needs the images, it waits for the result by calling Future.get. Ifwe're lucky, the results will already be ready by the time we ask; otherwise, at least we got a head start on downloading the images.
Callable和Future可以帮助我们表达合作线程之间的交互。在例6.13中的FutureRenderer,我们创建一个Callable来下载所有图像,并把它提交给一个ExecutorService,将会返回一个Future来描述任务的执行;当主任务执行到需要图像的时候,等待执行Future.get的结果。如果幸运的话,结果早就准备好了;或者,我们至少一开始就下载了图像。

The state-dependent nature of get means that the caller need not be aware of the state of the task, and the safe publication properties of task submission and result retrieval make this approach thread-safe. The exception handling code surrounding Future.get deals with two possible problems: that the task encountered an Exception, or the thread calling get was interrupted before the results were available. (See Sections 5.5.2 and 5.4.)
状态依赖的意思是调用任务者不比知道任务的状态,任务提交和结果查询的安全发布属性保证了这种方法的线程安全。围绕Future类的get方法的异常处理,主要处理两个问题:1、任务发生异常;2、调用get方法的线程在结果可用之前被中断。

FutureRenderer allows the text to be rendered concurrently with downloading the image data. When all the images are downloaded, they are rendered onto the page. This is an improvement in that the user sees a result quickly and it exploits some parallelism, but we can do considerably better. There is no need for users to wait for all the images to be downloaded; they would probably prefer to see individual images drawn as they become available.
FutureRenderer类使得文本渲染和下载图像并行进行。当所有的图像被下载,就会被渲染到页面上。这个是一个很大的进步,用户可以很快地看到结果,并且利用了并行,但是我们可以做得更好一点。用户不需要等到所有的图像都下载下来,他们更情愿看到是,当一个图像可以用的时候,就应该把它呈现到页面上。

6.3.4. Limitations of Parallelizing Heterogeneous Tasks
In the last example, we tried to execute two different types of tasks in parallel downloading the images and rendering the page. But obtaining significant performance improvements by trying to parallelize sequential heterogeneous tasks can be tricky.
在上一个例子中,我们试着将下载图像和页面渲染这两种不同的任务并行进行。不过为了获得性能的提高,试图将顺序的异种的任务进行并行处理,可能会比较棘手。

Two people can divide the work of cleaning the dinner dishes fairly effectively: one person washes while the other dries. However, assigning a different type of task to each worker does not scale well; if several more people show up, it is not obvious how they can help without getting in the way or significantly restructuring the division of labor. Without finding finer-grained parallelism among similar tasks, this approach will yield diminishing returns.
两个人将洗盘子的工作进行了公平有效的分解:一个洗,另一个人擦。然而,不同类型的任务分配给每个工人不能很好地衡量。如果在多几个人,并不能明显知道他们怎么样提供帮助,在不用阻碍或者显著地重新分配劳动力情况下。

A further problem with dividing heterogeneous tasks among multiple workers is that the tasks may have disparate sizes. If you divide tasks A and B between two workers but A takes ten times as long as B, you've only speeded up the total process by 9%. Finally, dividing a task among multiple workers always involves some amount of coordination overhead; for the division to be worthwhile, this overhead must be more than compensated by productivity improvements due to parallelism.
拆分多个工作者中的异种任务的另一个问题,就是每个任务的大小不同。如果你将任务A和B分给两个工人,但是任务A耗费的时间是任务B的10倍,你只提高了整个处理速度了9%。最后,分割一个任务给多个工作者设计到一些合作的开销。要使得分割是值得的,开销必须比并行带来的生产力的提升的花费要多。

FutureRenderer uses two tasks: one for rendering text and one for downloading the images. If rendering the text is much faster than downloading the images, as is entirely possible, the resulting performance is not much different from the sequential version, but the code is a lot more complicated. And the best we can do with two threads is speed things up by a factor of two. Thus, trying to increase concurrency by parallelizing heterogeneous activities can be a lot of work, and there is a limit to how much additional concurrency you can get out of it. (See Sections 11.4.2 and 11.4.3 for another example of the same phenomenon.)
FutureRenderer类使用了两个任务:一个渲染文本,另一个下载图像。如果渲染文本比下载图片快得多,这完全有可能,整个结果的性能和顺序版本的没有多大的区别,代码却更复杂。并且我们尽力使用2个线程来加速基于两个因素。因此,将异种任务并行处理来增加并发性可能会有很多工作要做,要避免这种情况,就需要对附加的并行性进行限制。
Listing 6.13. Waiting for Image Download with Future.

public class FutureRenderer {
    private final ExecutorService executor = ...;

    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) {
            // Re-assert the thread's interrupted status
            Thread.currentThread().interrupt();
            // We don't need the result, so cancel the task too
            future.cancel(true);
        } catch (ExecutionException e) {
            throw launderThrowable(e.getCause());
        }
    }
}





The real performance payoff of dividing a program's workload into tasks comes when there are a large number of independent, homogeneous tasks that can be processed concurrently.
当有大量的独立的、同种的可以并行处理任务的时候,将程序的负载拆分成多个任务来提高性能。

6.3.5. CompletionService: Executor Meets BlockingQueue
If you have a batch of computations to submit to an Executor and you want to retrieve their results as they become available, you could retain the Future associated with each task and repeatedly poll for completion by calling get with a timeout of zero. This is possible, but tedious. Fortunately there is a better way: a completion service.
如果你有一批计算提交给一个Executor,并且当结果可用的时候,你想查询他们的结果,你可以使用Future和每一个任务关联,你可以重复地使用get来获取完成的结果。这有可能成功,但是傻,幸运的是有一个更好的办法:CompletionService类。

CompletionService combines the functionality of an Executor and a BlockingQueue. You can submit Callable tasks to it for execution and use the queue like methods take and poll to retrieve completed results, packaged as Futures, as they become available. ExecutorCompletionService implements CompletionService, delegating the computation to an Executor.
CompletionService类是Executor和BlockingQueue功能的结合。你可以将Callable提交给CompletionService执行,并且使用类似queue的方法take和poll来检索完成的结果,像Futures一样包装,当它们可用的时候。ExecutorCompletionService类实现了CompletionService结果,委托一个Executor来进行计算。

The implementation of ExecutorCompletionService is quite straightforward. The constructor creates a BlockingQueue to hold the completed results. Future-Task has a done method that is called when the computation completes. When a task is submitted, it is wrapped with a QueueingFuture, a subclass of FutureTask that overrides done to place the result on the BlockingQueue, as shown in Listing 6.14. The take and poll methods delegate to the BlockingQueue, blocking if results are not yet available.
ExecutorCompletionService的实现是很直接的。构造函数中的BlockingQueue来保存完成的结果。当计算完成是FutureTask的done方法被调用。当一个任务提交,被封装在一个QueueingFuture中,QueueingFuture是FutureTask的子类,重写了done(),将结果放入到一个BlockingQueue中,如例6.14所示。take和poll方法委托给BlockingQueue,当结果没有准备好的时候,就处于阻塞。

Listing 6.14. QueueingFuture Class Used By ExecutorCompletionService.
private class QueueingFuture<V> extends FutureTask<V> {
    QueueingFuture(Callable<V> c) { super(c); }
    QueueingFuture(Runnable t, V r) { super(t, r); }

    protected void done() {
        completionQueue.add(this);
    }
}





6.3.6. Example: Page Renderer with CompletionService
We can use a CompletionService to improve the performance of the page renderer in two ways: shorter total runtime and improved responsiveness. We can create a separate task for downloading each image and execute them in a thread pool, turning the sequential download into a parallel one: this reduces the amount of time to download all the images. And by fetching results from the CompletionService and rendering each image as soon as it is available, we can give the user a more dynamic and responsive user interface. This implementation is shown in Renderer in Listing 6.15.
我们可以使用CompletionService以两种方式来提升页面渲染的的性能:减少整个运行时间和改进响应。我们可以为每一个图像下载创建单独的任务,在线程池中执行,将串行的下载转变成并行的下载;这就减少了下载所有图像的时间。一旦结果出来,就从CompletionService中取出结果来呈现每个图片,我们可以提供用户动态和快速反应的接口。这种实现如例6.15中的Render所示:

Listing 6.15. Using CompletionService to Render Page Elements as they Become Available.
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.
多个ExecutorCompletionServices可以共享一个Executor,所以当共享一个Executor的时候,明智的做法是为特殊的计算创建一个私有的ExecutorCompletionService。以这种方式使用的时候,CompletionService作为一批计算的处理器,如同Future作为一个计算的处理器一样。通过记住提交给CompletionService的任务的数目和统计多少完成的结果被检索了,你可以知道检索了提交计算的所有的结果,即使你使用的是共享的Executor.

6.3.7. 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.
有时,如果活动没有一定的时间内完成,它的结果已不再需要,可以放弃这个活动。例如,一个Web应用程序可以获取来自外部的广告服务器的广告,但如果广告在两秒钟内不可用,就显示默认的广告。所以广告无法使用不会破坏该网站的响应速度要求。同样,门户网站可以并行从多个数据源获取数据,如果超出一定的时间,就不会等待这个数据,直接渲染页面。

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.
执行任务使用时间预算的主要挑战是,确定你获取结果不会等太久,或者发现结果即将来临不会等太久。Future的get()支持这个要求,当结果准备好就返回,或者抛出TimeoutException,在规定的时间范围内结果不可用的情况下。

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 (see Chapter 7), it can be terminated early so as not to consume excessive resources. This technique is used in Listings 6.13 and 6.16.
第二个问题是使用规定时间的任务,在他们超时的时候停止他们,避免他们浪费计算资源。当超时的时候,可以通过严格管理自己的时间预算和中止任务。Future再一次提供了帮助,如果以
TimeoutException结束,可用通过Future结束任务。如果任务提供了撤销功能,它可以被轻易终止,避免消耗过多资源。如6.13和6.16所示.

Listing 6.16 shows a typical application of a timed Future.get. It generates a composite web page 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.[8] If the get times out, it cancels[9] the ad-fetching task and uses a default advertisement instead.
6.16展示了Future的get()的典型的应用。他创建了一个web页面由请求的内容和从广告服务器提取的广告组成。他提交了一个获取广告的任务给一个Executor,处理剩余的页面内容,等待广告在规定的时间内返回。如果超时了,它就取消获取广告的内容,使用默认的广告来代替。

[8] 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.

[9] The TRue parameter to Future.cancel means that the task thread can be interrupted if the task is currently running; see Chapter 7.

6.3.8. 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."
在上一节的时间预算的办法,可以很容易地推广到任意数量的任务。例如旅游预订门户:用户输入的旅行日期和要求,门户网站获取并显示从一些航空公司,酒店或汽车租赁公司的价格。根据该公司,获取出价可能涉及调用Web服务,咨询数据库,执行EDI事务,或其他一些机制。而不是由最慢的响应驱动页面的响应时间,它最好是呈现在一定时间内获取的可用的信息。对于未能在规定时间内响应的信息提供者,既可以忽略它们,也可以使用提示信息来提示,如“没有及时从JAVA机场获取到信息。”

Listing 6.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;
}

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 easier way invokeAll.
从一家公司获取价格相对于别的来说是独立的,所以获取价格是一个明智的任务界限,允许检索价格并行进行。很容易创建n个任务,提交给一个线程池,保持Futures,使用get(long timeout)来串行获取每个结果,不过还有更简单的方法invokeAll().

Listing 6.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.
例6.17使用了ExecutorService的invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)方法来提交多个任务,invokeAll()有一个tasks集合参数,返回结果是一个Future的集合,这两个集合有一样的结构;按照task集合中迭代的顺序,invokeAll添加Future到返回的集合中,因此允许调用者将一个Future和一个Callable关联起来。带时间限制的invokeAll()在所有任务完成以后将会返回,调用线程中断,或者超时。没有执行完成的任务在超时后会被取消。invokeAll()返回后,每个任务都结束了,或者被取消了。客户端可以调用get()或者isCancelled()来确定是哪个。

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.
围绕任务执行的应用程序可以简化开发和促进并行。Executor框架允许你分离任务的提交和执行策略,并支持不同的执行策略;每当你要创建一个线程来执行任务的时候,考虑使用一个Executor。为了最大限度地提高应用程序分解成任务的利益,你必须确定合理的任务边界。在某些应用中,明显的任务边界的工作很好,而在其他一些分析,可能需要发现细粒度利用并行。

Listing 6.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;
}





   
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics