The Executor Framework

6.2. The Executor Framework
Tasks are logical units of work, and threads are a mechanism by which tasks can run asynchronously. We've examined two policies for executing tasks using threads execute tasks sequentially in a single thread, and execute each task in its own thread. Both have serious limitations: the sequential approach suffers from poor responsiveness and throughput, and the thread-per-task approach suffers from poor resource management.
任务是逻辑工作单元,线程是任务可以异步运行机制。我们研究使了在单一的线程中按顺序执行任务,和在它自己的线程中为每个任务创建一个线程的两个政策。两者有很大的局限性:顺序的做法会导致反应迟钝和吞吐量小;每个任务一个线程的方法,导致资源管理不善。

In Chapter 5, we saw how to use bounded queues to prevent an overloaded application from running out of memory. Thread pools offer the same benefit for thread management, and java.util.concurrent provides a flexible thread pool implementation as part of the Executor framework. The primary abstraction for task execution in the Java class libraries is not Thread, but Executor, shown in Listing 6.3.
第5章中,我们看到了如何使用有限的队列,以防止从一个重负荷的应用程序耗尽内存。线程池为线程管理提供了相同的办法,作为Executor框架的一部分,java.util.concurrent中提供了一个灵活的线程池实现,Executor框架的一部分。在Java类库执行任务的主要抽象是不是Thread,而是Executor,如清单6.3所示。

Listing 6.3. Executor Interface.
public interface Executor {
void execute(Runnable command);
}
Executor may be a simple interface, but it forms the basis for a flexible and powerful framework for asynchronous task execution that supports a wide variety of task execution policies. It provides a standard means of decoupling task submission from task execution, describing tasks with Runnable. The Executor implementations also provide lifecycle support and hooks for adding statistics gathering, application management, and monitoring.
Executor看似一个简单的接口,但是它形成了一个灵活的、强大的框架的基础,这个框架用来执行异步任务,支持不同的任务执行策略。它提供了将任务的提交和任务的执行进行分离的标准,用Runnable来描述任务。Executor的实现类也提供生命周期支持和统计信息收集、应用管理和监视的工具。

Executor is based on the producer-consumer pattern, where activities that submit tasks are the producers (producing units of work to be done) and the threads that execute tasks are the consumers (consuming those units of work). Using an Executor is usually the easiest path to implementing a producer-consumer design in your application.
Executor是基于生产者-消费者模式的,提交任务的是生产者,执行任务的是消费者。使用Executor通常是在您的应用程序中进行生产者 - 消费者设计的最简单的路径。

6.2.1. Example: Web Server Using Executor
Building a web server with an Executor is easy. TaskExecutionWebServer in Listing 6.4 replaces the hard-coded thread creation with an Executor. In this case, we use one of the standard Executor implementations, a fixed-size thread pool with 100 threads.
使用Executor来构建Web Server很简单,例6-4中的TaskExecutionWebServer使用Executor取代了创建线程的硬编码(new Thread(Runnable r))。在这个例子中,我们使用了Executor的标准的实现类,一个线程数为100的线程池。

In TaskExecutionWebServer, submission of the request-handling task is decoupled from its execution using an Executor, and its behavior can be changed merely by substituting a different Executor implementation. Changing Executor implementations or configuration is far less invasive than changing the way tasks are submitted; Executor configuration is generally a one-time event and can easily be exposed for deployment-time configuration, whereas task submission code tends to be strewn throughout the program and harder to expose.
在TaskExecutionWebServer例子中,处理请求的任务的提交,通过使用Executor,已经从执行中分离出来,并且仅仅通过替换不同的Executor的实现,可以改变其行为。Executor实现或配置更改是远远低于改变任务提交方式侵入;Executor的配置一般是一次性事件,并可以很容易在部署时的配置,而任务提交的代码往往是布满整个程序,并且难以发现。


Listing 6.4. Web Server Using a Thread Pool.
class TaskExecutionWebServer {
private static final int NTHREADS = 100;
private static final Executor exec
= Executors.newFixedThreadPool(NTHREADS);

public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
};
exec.execute(task);
}
}
}





We can easily modify TaskExecutionWebServer to behave like ThreadPer-TaskWebServer by substituting an Executor that creates a new thread for each request. Writing such an Executor is trivial, as shown in ThreadPerTaskExecutor in Listing 6.5.
我们可以容易修改TaskExecutionWebServer的行为像ThreadPer-TaskWebServer一样,为每个请求创建一个新线程。写这样一个Executor是微不足道的。如例6.5中ThreadPerTaskExecutor。


Listing 6.5. Executor that Starts a New Thread for Each Task.
public class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
};
}





Similarly, it is also easy to write an Executor that would make TaskExecutionWebServer behave like the single-threaded version, executing each task synchronously before returning from execute, as shown in WithinThreadExecutor in Listing 6.6.
同样,它也可以很容易实现一个像TaskExecutionWebServer一样的单线程版本,同步执行每项任务在从execute方法返回之前。如例6.6 WithinThreadExecutor。

6.2.2. Execution Policies
The value of decoupling submission from execution is that it lets you easily specify, and subsequently change without great difficulty, the execution policy for a given class of tasks. An execution policy specifies the "what, where, when, and how" of task execution, including:
任务的提交和执行分离的价值就是让你能够不困难地轻松指定,和随后更改一个给定任务的执行策略。一个任务的执行策略就是“什么、地点、时间、如何做”,包括:

Listing 6.6. Executor that Executes Tasks Synchronously in the Calling Thread.
public class WithinThreadExecutor implements Executor {
public void execute(Runnable r) {
r.run();
};
}





In what thread will tasks be executed?
任务在什么线程中被执行。

In what order should tasks be executed (FIFO, LIFO, priority order)?
任务被执行的顺序是什么。

How many tasks may execute concurrently?
多少个任务被并行执行。

How many tasks may be queued pending execution?
多少个任务在队列中被挂起执行。

If a task has to be rejected because the system is overloaded, which task should be selected as the victim, and how should the application be notified?
如果因为系统超载,一个任务被拒绝。哪个任务应被选择,应如何通知应用程序?

What actions should be taken before or after executing a task?
在任务执行前后应该进行哪些操作。

Execution policies are a resource management tool, and the optimal policy depends on the available computing resources and your quality-of-service requirements. By limiting the number of concurrent tasks, you can ensure that the application does not fail due to resource exhaustion or suffer performance problems due to contention for scarce resources.[3] Separating the specification of execution policy from task submission makes it practical to select an execution policy at deployment time that is matched to the available hardware.
执行政策是一个资源管理工具,最优策略取决于可用的计算资源和服务质量的要求。通过并发任务数的限制,可以确保应用程序不因资源枯竭而失败或遭受性能问题,由于对稀缺资源的争夺。分离任务提交执行政策的规范使得它的实际选择在部署时是可用的硬件相匹配的执行政策。


[3] This is analogous to one of the roles of a transaction monitor in an enterprise application: it can throttle the rate at which transactions are allowed to proceed so as not to exhaust or overstress limited resources.
这是类似于在企业应用程序的事务监视器的角色之一:它可以扼杀在哪个事务被允许继续进行,以便尽量不要用尽或过分强调有限的资源率。

Whenever you see code of the form:
当你看到下面的代码:

new Thread(runnable).start()


and you think you might at some point want a more flexible execution policy, seriously consider replacing it with the use of an Executor.
你认为你可能在某些时候需要更灵活的执行政策,认真考虑使用Executor来代替它。


6.2.3. Thread Pools
A thread pool, as its name suggests, manages a homogeneous pool of worker threads. A thread pool is tightly bound to a work queue holding tasks waiting to be executed. Worker threads have a simple life: request the next task from the work queue, execute it, and go back to waiting for another task.
线程池,顾名思义,管理一批性质相同的工作线程。线程池和一个工作队列紧密结合,该队列持有等待执行的任务。工作线程有一个简单的生活:要求工作队列中的下一个任务,执行任务,回去等待另一个任务。

Executing tasks in pool threads has a number of advantages over the thread-per-task approach. Reusing an existing thread instead of creating a new one amortizes thread creation and teardown costs over multiple requests. As an added bonus, since the worker thread often already exists at the time the request arrives, the latency associated with thread creation does not delay task execution, thus improving responsiveness. By properly tuning the size of the thread pool, you can have enough threads to keep the processors busy while not having so many that your application runs out of memory or thrashes due to competition among threads for resources.
在线程池中执行任务比起每个任务一个线程有很多优点。重用现有的线程,而不是创建新的线程,避免了线程的创建和拆卸的开销。作为额外的奖励,因为工作线程在请求到达的时间往往已经存在,避免了因为创建线程的延迟而延迟执行任务,从而提高了反应。通过适当调整线程池的大小,你可以有足够的线程来保持处理器的繁忙,而不必这么多到耗尽内存或者为了争夺资源而竞争。
The class library provides a flexible thread pool implementation along with some useful predefined configurations. You can create a thread pool by calling one of the static factory methods in Executors:
JAVA类库提供了一个灵活的线程池实现,以及一些有用的预定义配置。你可以通过调用Executors类的静态工厂方法来创建一个线程池:

newFixedThreadPool. A fixed-size thread pool creates threads as tasks are submitted, up to the maximum pool size, and then attempts to keep the pool size constant (adding new threads if a thread dies due to an unexpected Exception).
newFixedThreadPool:当有任务提交时,一个固定线程数的线程池来创建线程,直到创建的线程大道最大限制,并保持线程池大小不变。(当有线程由于未知异常而死亡的时候,会创建新的线程来代替)

newCachedThreadPool. A cached thread pool has more flexibility to reap idle threads when the current size of the pool exceeds the demand for processing, and to add new threads when demand increases, but places no bounds on the size of the pool.
newCachedThreadPool:一个cached线程池更加灵活,当线程池的大小大于请求的时候,它会重用闲置的线程;当请求增加的时候,同样会添加新的线程。不过对于线程池的大小没有限制。

newSingleThreadExecutor. A single-threaded executor creates a single worker thread to process tasks, replacing it if it dies unexpectedly. Tasks are guaranteed to be processed sequentially according to the order imposed by the task queue (FIFO, LIFO, priority order).[4]
newSingleThreadExecutor:一个单线程线程池创建一个单一的线程来处理任务,如果由于未知原因导致线程死亡,则重新创建一个线程来代替。这样就保证了任务按照队列的顺序来按顺序执行。

[4] Single-threaded executors also provide sufficient internal synchronization to guarantee that any memory writes made by tasks are visible to subsequent tasks; this means that objects can be safely confined to the "task thread" even though that thread may be replaced with another from time to time.


newScheduledThreadPool. A fixed-size thread pool that supports delayed and periodic task execution, similar to Timer. (See Section 6.2.5.)
newScheduledThreadPool:一个固定大小的线程池,支持延迟和定期的执行任务,类似计时器

The newFixedThreadPool and newCachedThreadPool factories return instances of the general-purpose ThreadPoolExecutor, which can also be used directly to construct more specialized executors. We discuss thread pool configuration options in depth in Chapter 8.
newFixedThreadPool和newCachedThreadPool工厂返回的是多用途的ThreadPoolExecutor类的实例。ThreadPoolExecutor可以直接构造更加专业的线程池。我们将在第8章来讨论线程池的设置问题。

The web server in TaskExecutionWebServer uses an Executor with a bounded pool of worker threads. Submitting a task with execute adds the task to the work queue, and the worker threads repeatedly dequeue tasks from the work queue and execute them.
TaskExecutionWebServer的WEB服务器使用工作线程数限制的线程池。提交任务至工作队列,工作线程重复地从工作队列中获取任务并执行。

Switching from a thread-per-task policy to a pool-based policy has a big effect on application stability: the web server will no longer fail under heavy load.[5] It also degrades more gracefully, since it does not create thousands of threads that compete for limited CPU and memory resources. And using an Executor opens the door to all sorts of additional opportunities for tuning, management, monitoring, logging, error reporting, and other possibilities that would have been far more difficult to add without a task execution framework.
从每个任务一个线程转换至线程池,对于应用程序的稳定性有很大的作用。在负载很重的情况下,服务器永远不会崩溃。它总是优雅退出,因为它不会创建成千上万个线程,而导致CPU竞争和资源耗尽。使用线程池为调试、管理,监测,记录,错误报告等带来了机遇,也为其他一些如果不使用任务执行框架就会很困难的应用提供了机会。

[5] While the server may not fail due to the creation of too many threads, if the task arrival rate exceeds the task service rate for long enough it is still possible (just harder) to run out of memory because of the growing queue of Runnables awaiting execution. This can be addressed within the Executor framework by using a bounded work queuesee Section 8.3.2.

6.2.4. Executor Lifecycle
We've seen how to create an Executor but not how to shut one down. An Executor implementation is likely to create threads for processing tasks. But the JVM can't exit until all the (nondaemon) threads have terminated, so failing to shut down an Executor could prevent the JVM from exiting.
我们看到了如何创建一个Executor,但是没有说明如何关闭它。一个Executor的实现有可能创建线程来处理任务。但是直到所有的线程关闭了,JVM才会退出。所以Executor关闭失败将导致JVM退出失败。

Because an Executor processes tasks asynchronously, at any given time the state of previously submitted tasks is not immediately obvious. Some may have completed, some may be currently running, and others may be queued awaiting execution. In shutting down an application, there is a spectrum from graceful shutdown (finish what you've started but don't accept any new work) to abrupt shutdown (turn off the power to the machine room), and various points in between. Since Executors provide a service to applications, they should be able to be shut down as well, both gracefully and abruptly, and feed back information to the application about the status of tasks that were affected by the shutdown.
因为一个Executor异步处理任务,在任何特定时间以前提交的任务的状态不是立即明显。有些人可能已经完成,有的可能正在运行的,以及其他可能正在排队等待执行。在关闭应用程序,有优雅关机(结束你所有启动的任务,不再接受新任务)和强制关机(关闭电源)。由于Executor提供服务给应用程序,他们应该能够被关闭,不管是优雅还是突然,并反馈信息给应用程序,告知被关闭影响的任务状态。

To address the issue of execution service lifecycle, the ExecutorService interface extends Executor, adding a number of methods for lifecycle management (as well as some convenience methods for task submission). The lifecycle management methods of ExecutorService are shown in Listing 6.7.
为了解决执行服务生命周期的问题,ExecutorService接口扩展了Executor,增加了一些生命周期管理方法(以及任务提交一些方便的方法)。 ExecutorService的生命周期管理方法,如例子6.7所示。
Listing 6.7. Lifecycle Methods in ExecutorService.
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// ... additional convenience methods for task submission
}





The lifecycle implied by ExecutorService has three statesrunning, shutting down, and terminated. ExecutorServices are initially created in the running state. The shutdown method initiates a graceful shutdown: no new tasks are accepted but previously submitted tasks are allowed to completeincluding those that have not yet begun execution. The shutdownNow method initiates an abrupt shutdown: it attempts to cancel outstanding tasks and does not start any tasks that are queued but not begun.
由ExecutorService的暗示的生命周期有三种状态,运行,关闭,和终止。 ExecutorServices最初创建在运行状态。shutdown方法启动正常关机:不接受新任务,但以前提交的任务可以完成,包括那些尚未开始执行。shutdownNow方法发起突然关机:它试图取消未完成的任务,不启动任何队列中还没有开始的任务。

Tasks submitted to an ExecutorService after it has been shut down are handled by the rejected execution handler (see Section 8.3.3), which might silently discard the task or might cause execute to throw the unchecked RejectedExecutionException. Once all tasks have completed, the ExecutorService TRansitions to the terminated state. You can wait for an ExecutorService to reach the terminated state with awaitTermination, or poll for whether it has yet terminated with isTerminated. It is common to follow shutdown immediately by awaitTermination, creating the effect of synchronously shutting down the ExecutorService.(Executor shutdown and task cancellation are covered in more detail in Chapter 7.)
ExecutorService执行关闭操作后,提交给它的任务被“拒绝执行处理器”来处理,任务可能被悄悄抛弃,或者抛出RejectedExecutionException异常。一旦所有的任务已经关闭,ExecutorService转变成结束状态。你可以使用awaitTermination方法来等待一个ExecutorService到达结束状态,或者通过isTerminated方法来查看它的结束状态。常用的做法是使用awaitTermination来进行立即关闭,创建同步关停的ExecutorService的效果。(Executor关闭和取消任务将在第7章中进行详细阐述)
LifecycleWebServer in Listing 6.8 extends our web server with lifecycle support. It can be shut down in two ways: programmatically by calling stop, and through a client request by sending the web server a specially formatted HTTP request.
例子6.8中的LifecycleWebServer通过生命周期支持扩展我们的网站服务器。它可以关闭在两个方面:以编程方式调用stop方法,或者通过客户端请求由Web服务器发送一个特殊格式的HTTP请求。

Listing 6.8. Web Server with Shutdown Support.
class LifecycleWebServer {
private final ExecutorService exec = ...;

public void start() throws IOException {
ServerSocket socket = new ServerSocket(80);
while (!exec.isShutdown()) {
try {
final Socket conn = socket.accept();
exec.execute(new Runnable() {
public void run() { handleRequest(conn); }
});
} catch (RejectedExecutionException e) {
if (!exec.isShutdown())
log("task submission rejected", e);
}
}
}

public void stop() { exec.shutdown(); }

void handleRequest(Socket connection) {
Request req = readRequest(connection);
if (isShutdownRequest(req))
stop();
else
dispatchRequest(req);
}
}





6.2.5. Delayed and Periodic Tasks
The Timer facility manages the execution of deferred ("run this task in 100 ms") and periodic ("run this task every 10 ms") tasks. However, Timer has some drawbacks, and ScheduledThreadPoolExecutor should be thought of as its replacement.[6] You can construct a ScheduledThreadPoolExecutor through its constructor or through the newScheduledThreadPool factory.
Timer类灵巧的管理者任务的延期执行和周期执行。然而Timer也有一些缺点,ScheduledThreadPoolExecutor可以被认为是Time的替代。你可以通过ScheduledThreadPoolExecutor的构造器或者Executors的newScheduledThreadPool方法来获得ScheduledThreadPoolExecutor的实例。

[6] Timer does have support for scheduling based on absolute, not relative time, so that tasks can be sensitive to changes in the system clock; ScheduledThreadPoolExecutor supports only relative time.
Timer只支持绝对时间的基础上的调度,而不是相对的时间,所以任务对系统时钟的变化敏感; ScheduledThreadPoolExecutor只支持相对时间。

A Timer creates only a single thread for executing timer tasks. If a timer task takes too long to run, the timing accuracy of other TimerTasks can suffer. If a recurring TimerTask is scheduled to run every 10 ms and another Timer-Task takes 40 ms to run, the recurring task either (depending on whether it was scheduled at fixed rate or fixed delay) gets called four times in rapid succession after the long-running task completes, or "misses" four invocations completely. Scheduled thread pools address this limitation by letting you provide multiple threads for executing deferred and periodic tasks.
一个Timer只创建一个单独的线程执行定时任务。如果一个定时器任务的运行时间过长,其他定时器任务会遭受计时精度文帝。如果一个经常性的TimerTask计划运行每10毫秒,和另一个定时器任务需要40毫秒运行,周期性任务(不管是在固定利率或固定延迟预定)被调用后,可能会在长时间任务完成后接连运行4次,或“错过”四个调用完全。预定的线程池解决这个限制,让你提供多个线程执行的延期和定期任务。

Another problem with Timer is that it behaves poorly if a TimerTask throws an unchecked exception. The Timer thread doesn't catch the exception, so an unchecked exception thrown from a TimerTask terminates the timer thread. Timer also doesn't resurrect the thread in this situation; instead, it erroneously assumes the entire Timer was cancelled. In this case, TimerTasks that are already scheduled but not yet executed are never run, and new tasks cannot be scheduled. (This problem, called "thread leakage" is described in Section 7.3, along with techniques for avoiding it.)
Timer的另一个问题是,在一个TimerTask抛出一个未经检查的异常的情况下,Timer表现很差。定时器线程不捕获异常,所以从一个TimerTask抛出一个未经检查的异常终止Timer线程。定时器在这种情况下不会复活线程,而是错误地假定整个定时器被取消。在这种情况下,已安排但尚未执行的TimerTasks从未运行,新任务不能被安排。 (这个问题,被称为“线程泄漏”描述在7.3节,以及避免它的技术。)

OutOfTime in Listing 6.9 illustrates how a Timer can become confused in this manner and, as confusion loves company, how the Timer shares its confusion with the next hapless caller that tries to submit a TimerTask. You might expect the program to run for six seconds and exit, but what actually happens is that it terminates after one second with an IllegalStateException whose message text is "Timer already cancelled". ScheduledThreadPoolExecutor deals properly with ill-behaved tasks; there is little reason to use Timer in Java 5.0 or later.
OutOfTime在例6.9说明一个Timer在这种方式下混淆,混乱爱陪伴,定时器如何下一个倒霉的来电,尝试提交一个TimerTask的混乱。您可能希望程序运行6秒后,但实际情况是,后一秒钟“定时器已取消”的消息文本是一个IllegalStateException终止。 ScheduledThreadPoolExecutor妥善处理了这种病态行为的任务,没有理由在Java5.0或更高版本中使用Timer。

If you need to build your own scheduling service, you may still be able to take advantage of the library by using a DelayQueue, a BlockingQueue implementation that provides the scheduling functionality of ScheduledThreadPoolExecutor. A DelayQueue manages a collection of Delayed objects. A Delayed has a delay time associated with it: DelayQueue lets you take an element only if its delay has expired. Objects are returned from a DelayQueue ordered by the time associated with their delay.
如果你需要建立自己的调度服务,您可能仍然能够利用JAVA类库中的DelayQueue,一个BlockingQueue的实现提供的ScheduledThreadPoolExecutor的调度功能的优势。一个的DelayQueue管理着延迟对象的集合。一个延迟对象与它的延迟时间相关。DelayQueue让你获取它的延迟已过期的元素。DelayQueue返回的对象以他们的延迟时间来排序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值