A Guide to the Java ExecutorService – Java ExecutorService指南

最后修改: 2016年 2月 23日

1. Overview

1.概述

ExecutorService is a JDK API that simplifies running tasks in asynchronous mode. Generally speaking, ExecutorService automatically provides a pool of threads and an API for assigning tasks to it.

ExecutorService是一个JDK API,它简化了在异步模式下运行任务。一般来说,ExecutorService会自动提供一个线程池和一个用于将任务分配给它的API。

2. Instantiating ExecutorService

2.实例化ExecutorService

2.1. Factory Methods of the Executors Class

2.1.执行者类的工厂方法

The easiest way to create ExecutorService is to use one of the factory methods of the Executors class.

创建ExecutorService的最简单方法是使用Executors类中的一个工厂方法。

For example, the following line of code will create a thread pool with 10 threads:

例如,下面一行代码将创建一个有10个线程的线程池。

ExecutorService executor = Executors.newFixedThreadPool(10);

There are several other factory methods to create a predefined ExecutorService that meets specific use cases. To find the best method for your needs, consult Oracle’s official documentation.

还有其他几种工厂方法可以创建符合特定使用情况的预定义 ExecutorService。要找到符合您需求的最佳方法,请查阅Oracle的官方文档

2.2. Directly Create an ExecutorService

2.2.直接创建一个ExecutorService

Because ExecutorService is an interface, an instance of any its implementations can be used. There are several implementations to choose from in the java.util.concurrent package, or you can create your own.

因为ExecutorService是一个接口,可以使用其任何实现的实例。在java.util.concurrent包中有几个实现可供选择,或者您可以创建自己的实现。

For example, the ThreadPoolExecutor class has a few constructors that we can use to configure an executor service and its internal pool:

例如,ThreadPoolExecutor类有几个构造函数,我们可以用来配置一个执行器服务及其内部池。

ExecutorService executorService = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,   
  new LinkedBlockingQueue<Runnable>());

You may notice that the code above is very similar to the source code of the factory method newSingleThreadExecutor(). For most cases, a detailed manual configuration isn’t necessary.

你可能注意到,上面的代码与工厂方法newSingleThreadExecutor()的源代码非常相似。对于大多数情况,详细的手动配置是没有必要的。

3. Assigning Tasks to the ExecutorService

3.将任务分配给ExecutorService

ExecutorService can execute Runnable and Callable tasks. To keep things simple in this article, two primitive tasks will be used. Notice that we use lambda expressions here instead of anonymous inner classes:

ExecutorService可以执行RunnableCallable任务。在本文中,为了保持简单,将使用两个原始的任务。请注意,我们在这里使用lambda表达式,而不是匿名的内部类。

Runnable runnableTask = () -> {
    try {
        TimeUnit.MILLISECONDS.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Callable<String> callableTask = () -> {
    TimeUnit.MILLISECONDS.sleep(300);
    return "Task's execution";
};

List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);

We can assign tasks to the ExecutorService using several methods including execute(), which is inherited from the Executor interface, and also submit(), invokeAny() and invokeAll().

我们可以使用几个方法将任务分配给ExecutorService,包括execute(),这是从Executor接口继承的,还有submit() invokeAny()invokeAll()

The execute() method is void and doesn’t give any possibility to get the result of a task’s execution or to check the task’s status (is it running):

execute()方法是void,没有提供任何可能性来获得任务的执行结果或检查任务的状态(是否正在运行)。

executorService.execute(runnableTask);

submit() submits a Callable or a Runnable task to an ExecutorService and returns a result of type Future:

submit()ExecutorService提交一个CallableRunnable任务,并返回一个Future类型的结果。

Future<String> future = 
  executorService.submit(callableTask);

invokeAny() assigns a collection of tasks to an ExecutorService, causing each to run, and returns the result of a successful execution of one task (if there was a successful execution):

invokeAny()将一个任务集合分配给ExecutorService,使每个任务运行,并返回一个任务的成功执行结果(如果有一个成功的执行)。

String result = executorService.invokeAny(callableTasks);

invokeAll() assigns a collection of tasks to an ExecutorService, causing each to run, and returns the result of all task executions in the form of a list of objects of type Future:

invokeAll()将一个任务集合分配给ExecutorService,使每个任务运行,并以Future类型对象列表的形式返回所有任务执行的结果。

List<Future<String>> futures = executorService.invokeAll(callableTasks);

Before going further, we need to discuss two more items: shutting down an ExecutorService and dealing with Future return types.

在进一步讨论之前,我们还需要讨论两个项目:关闭ExecutorService和处理Future返回类型。

4. Shutting Down an ExecutorService

4.关闭一个执行器服务

In general, the ExecutorService will not be automatically destroyed when there is no task to process. It will stay alive and wait for new work to do.

一般来说,当没有任务需要处理时,ExecutorService不会被自动销毁。它将保持活力,等待新的工作。

In some cases this is very helpful, such as when an app needs to process tasks that appear on an irregular basis or the task quantity is not known at compile time.

在某些情况下,这是很有帮助的,例如当一个应用程序需要处理不定期出现的任务,或者在编译时不知道任务数量。

On the other hand, an app could reach its end but not be stopped because a waiting ExecutorService will cause the JVM to keep running.

另一方面,一个应用程序可以达到其终点,但不会被停止,因为一个等待的ExecutorService将导致JVM继续运行。

To properly shut down an ExecutorService, we have the shutdown() and shutdownNow() APIs.

为了正确关闭ExecutorService,我们有shutdown()shutdownNow() API。

The shutdown() method doesn’t cause immediate destruction of the ExecutorService. It will make the ExecutorService stop accepting new tasks and shut down after all running threads finish their current work:

shutdown()方法并不会导致ExecutorService的立即销毁。它将使ExecutorService停止接受新的任务,并在所有运行的线程完成其当前工作后关闭。

executorService.shutdown();

The shutdownNow() method tries to destroy the ExecutorService immediately, but it doesn’t guarantee that all the running threads will be stopped at the same time:

shutdownNow()方法试图立即销毁ExecutorService,但它不能保证所有正在运行的线程都能同时停止。

List<Runnable> notExecutedTasks = executorService.shutDownNow();

This method returns a list of tasks that are waiting to be processed. It is up to the developer to decide what to do with these tasks.

该方法返回一个等待处理的任务列表。由开发者来决定如何处理这些任务。

One good way to shut down the ExecutorService (which is also recommended by Oracle) is to use both of these methods combined with the awaitTermination() method:

关闭ExecutorService(这也是Oracle推荐的)的一个好方法是使用这两个方法与awaitTermination()方法相结合。

executorService.shutdown();
try {
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
        executorService.shutdownNow();
    } 
} catch (InterruptedException e) {
    executorService.shutdownNow();
}

With this approach, the ExecutorService will first stop taking new tasks and then wait up to a specified period of time for all tasks to be completed. If that time expires, the execution is stopped immediately.

采用这种方法,ExecutorService将首先停止接受新的任务,然后在指定的时间内等待所有任务的完成。如果这段时间过了,就会立即停止执行。

5. The Future Interface

5.未来界面

The submit() and invokeAll() methods return an object or a collection of objects of type Future, which allows us to get the result of a task’s execution or to check the task’s status (is it running).

submit() invokeAll()方法返回一个对象或一个Future类型的对象集合,这使我们能够获得一个任务的执行结果或检查任务的状态(是否正在运行)。

The Future interface provides a special blocking method get(), which returns an actual result of the Callable task’s execution or null in the case of a Runnable task:

Future接口提供了一个特殊的阻塞方法get(),它返回Callable任务的实际执行结果,或者在Runnable任务的情况下返回null

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
    result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Calling the get() method while the task is still running will cause execution to block until the task properly executes and the result is available.

在任务仍在运行时调用get()方法将导致执行阻塞,直到任务正确执行且结果可用。

With very long blocking caused by the get() method, an application’s performance can degrade. If the resulting data is not crucial, it is possible to avoid such a problem by using timeouts:

由于get()方法造成了很长的阻塞,应用程序的性能会下降。如果得到的数据并不重要,可以通过使用超时来避免这种问题。

String result = future.get(200, TimeUnit.MILLISECONDS);

If the execution period is longer than specified (in this case, 200 milliseconds), a TimeoutException will be thrown.

如果执行时间超过了指定的时间(在本例中是200毫秒),将抛出一个TimeoutException

We can use the isDone() method to check if the assigned task already processed or not.

我们可以使用isDone()方法来检查分配的任务是否已经处理。

The Future interface also provides for canceling task execution with the cancel() method and checking the cancellation with the isCancelled() method:

Future接口还提供了用cancel()方法取消任务执行,并用isCancelled()方法检查取消情况。

boolean canceled = future.cancel(true);
boolean isCancelled = future.isCancelled();

6. The ScheduledExecutorService Interface

6、ScheduledExecutorService接口

The ScheduledExecutorService runs tasks after some predefined delay and/or periodically.

ScheduledExecutorService在一些预定的延迟和/或定期运行任务。

Once again, the best way to instantiate a ScheduledExecutorService is to use the factory methods of the Executors class.

再次,实例化ScheduledExecutorService的最佳方式是使用Executors类的工厂方法。

For this section, we use a ScheduledExecutorService with one thread:

在本节中,我们使用一个有一个线程的ScheduledExecutorService

ScheduledExecutorService executorService = Executors
  .newSingleThreadScheduledExecutor();

To schedule a single task’s execution after a fixed delay, use the scheduled() method of the ScheduledExecutorService.

要在一个固定的延迟后安排单个任务的执行,请使用ScheduledExecutorServicescheduled()方法。

Two scheduled() methods allow you to execute Runnable or Callable tasks:

两个scheduled()方法允许你执行RunnableCallable任务。

Future<String> resultFuture = 
  executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

The scheduleAtFixedRate() method lets us run a task periodically after a fixed delay. The code above delays for one second before executing callableTask.

scheduleAtFixedRate()方法让我们在一个固定的延迟后定期运行一个任务。上面的代码在执行callableTask之前延迟了一秒。

The following block of code will run a task after an initial delay of 100 milliseconds. And after that, it will run the same task every 450 milliseconds:

下面的代码块将在100毫秒的初始延迟后运行一个任务。此后,它将每隔450毫秒运行一次相同的任务。

Future<String> resultFuture = service
  .scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

If the processor needs more time to run an assigned task than the period parameter of the scheduleAtFixedRate() method, the ScheduledExecutorService will wait until the current task is completed before starting the next.

如果处理器需要比period方法的scheduleAtFixedRate()参数更多的时间来运行一个分配的任务,ScheduledExecutorService将等待,直到当前任务完成后再开始下一个。

If it is necessary to have a fixed length delay between iterations of the task, scheduleWithFixedDelay() should be used.

如果有必要在任务的迭代之间有一个固定长度的延迟,应该使用scheduleWithFixedDelay()

For example, the following code will guarantee a 150-millisecond pause between the end of the current execution and the start of another one:

例如,下面的代码将保证在当前执行的结束和另一个执行的开始之间有150毫秒的停顿。

service.scheduleWithFixedDelay(task, 100, 150, TimeUnit.MILLISECONDS);

According to the scheduleAtFixedRate() and scheduleWithFixedDelay() method contracts, period execution of the task will end at the termination of the ExecutorService or if an exception is thrown during task execution.

根据scheduleAtFixedRate()scheduleWithFixedDelay()方法合同,任务的周期执行将在ExecutorService终止时或在任务执行期间抛出异常时结束

7. ExecutorService vs Fork/Join

7.ExecutorService vs Fork/Join

After the release of Java 7, many developers decided to replace the ExecutorService framework with the fork/join framework.

在Java 7发布后,许多开发者决定用fork/join框架取代ExecutorService框架。

This is not always the right decision, however. Despite the simplicity and frequent performance gains associated with fork/join, it reduces developer control over concurrent execution.

然而,这并不总是正确的决定。尽管fork/join的简单性和频繁的性能提升,它减少了开发者对并发执行的控制。

ExecutorService gives the developer the ability to control the number of generated threads and the granularity of tasks that should be run by separate threads. The best use case for ExecutorService is the processing of independent tasks, such as transactions or requests according to the scheme “one thread for one task.”

ExecutorService使开发者能够控制生成线程的数量以及应由独立线程运行的任务的粒度。ExecutorService的最佳用例是处理独立的任务,例如按照 “一个线程一个任务 “的方案处理事务或请求。

In contrast, according to Oracle’s documentation, fork/join was designed to speed up work that can be broken into smaller pieces recursively.

相比之下,根据Oracle的文档,fork/join的设计是为了加快那些可以递归地分解成小块的工作。

8. Conclusion

8.结论

Despite the relative simplicity of ExecutorService, there are a few common pitfalls.

尽管ExecutorService相对简单,但也有一些常见的陷阱。

Let’s summarize them:

让我们对它们进行总结。

Keeping an unused ExecutorService alive: See the detailed explanation in Section 4 on how to shut down an ExecutorService.

保持未使用的ExecutorService的活力:关于如何关闭ExecutorService,请参阅第4节的详细解释。

Wrong thread-pool capacity while using fixed length thread pool: It is very important to determine how many threads the application will need to run tasks efficiently. A too-large thread pool will cause unnecessary overhead just to create threads that will mostly be in the waiting mode. Too few can make an application seem unresponsive because of long waiting periods for tasks in the queue.

使用固定长度的线程池时线程池的容量不对:确定应用程序需要多少个线程来有效地运行任务是非常重要的。过大的线程池会导致不必要的开销,而这些线程大多处于等待模式。太少的线程会使应用程序看起来没有反应,因为队列中的任务有很长的等待时间。

Calling a Future‘s get() method after task cancellation: Attempting to get the result of an already canceled task triggers a CancellationException.

任务取消后调用Futureget()方法。试图获取一个已经取消的任务的结果会触发一个CancellationException

Unexpectedly long blocking with Future‘s get() method: We should use timeouts to avoid unexpected waits.

使用Futureget()方法进行意外的长时间阻塞。我们应该使用超时来避免意外的等待。

As always, the code for this article is available in the GitHub repository.

一如既往,本文的代码可在GitHub 仓库中找到。