Update 80. executor 、task 和 stream 优先于线程.md

This commit is contained in:
Joe 2019-04-05 14:34:05 +08:00 committed by GitHub
parent 0d0cc92305
commit 7eb35d9da8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -20,15 +20,15 @@ exec.execute(runnable);
exec.shutdown();
```
  你可以利用 executor service 完成更多的工作。例如,可以等待完成一项特殊的任务(就如第 79 条中的 get 方法一样),你可以等待一个任务集合中的任何任务或者所有任务完成(利用 invokeAny 或者 invokeAll 方法),可以等待 executor service 优雅地完成终止(利用 awaitTermination 方法),可以在任务完成时逐个地获取这些任务的结果(利用 ExecutorCompletionService ),可以调度在某个特殊的时间段定时运行或者阶段性地运行的任务(利用 ScheduledThreadPoolExecutor ),等等。
  你可以利用 executor service 完成更多的工作。例如,可以等待完成一项特殊的任务(就如第 79 条中的 get 方法一样),你可以等待一个任务集合中的任何任务或者所有任务完成(利用 invokeAny 或者 invokeAll 方法),可以等待 executor service 优雅地完成终止(利用 awaitTermination 方法),可以在任务完成时逐个地获取这些任务的结果(利用 ExecutorCompletionService可以调度在某个特殊的时间段定时运行或者阶段性地运行的任务利用 ScheduledThreadPoolExecutor等等。
  如果想让不止一个线程来处理来自这个队列的请求,只要调用一个不同的静态工厂,这个工厂创建了一种不同的 executor service ,称作线程池( thread pool 。你可以用固定或者可变数目的线程创建一个线程池。java.util.concurrent.Executors 类包含了静态工厂,能为你提供所需的大多数以 excutor 。然而,如果你想来点特别的,可以直接使用 ThreadPoolExecutor 类。这个类允许你控制线程池操作的几乎每个方面。
  如果想让不止一个线程来处理来自这个队列的请求,只要调用一个不同的静态工厂,这个工厂创建了一种不同的 executor service 称作线程池thread pool 。你可以用固定或者可变数目的线程创建一个线程池。java.util.concurrent.Executors 类包含了静态工厂,能为你提供所需的大多数以 excutor 。然而,如果你想来点特别的,可以直接使用 ThreadPoolExecutor 类。这个类允许你控制线程池操作的几乎每个方面。
  为特殊的应用程序选择 executor service 是很有技巧的。如果编写的是小程序,或者是轻量负载的服务器,使用 Executors.newCachedThreadPool 通常是个不错的选择,因为它不需要配置,并且一般情况下能够正确地完成工作。但是对于大负载的服务器来说,缓存的线程池就不是很好的选择了!在缓存的线程池中,被提交的任务没有排成队列,而是直接交给线程执行。如果没有线程可用,就创建一个新的线程。如果服务器负载得太重,以致它所有的 CPU 都完全被占用了,当有更多的任务时,就会创建更多的线程,这样只会使情况变得更糟。因此,在大负载的产品服务器中,最好使用 Executors.newFixedThreadPool ,它为你提供了一个包含固定线程数目的线程池,或者为了最大限度地控制它,就直接使用 ThreadPoolExecutor 类。
  不仅应该尽量不要编写自己的工作队列,而且还应该尽量不直接使用线程。当直接使用线程时, Thread 是既充当工作单元,又是执行机制。在 Executor Framework 中,工作单元和执行机制是分开的。现在关键的抽象是工作单元,称作任务( task 。任务有两种Runnable 及其近亲 Callable (它与 Runnable 类似,但它会返回值,并且能够抛出任意的异常) 。执行任务的通用机制是 executor service 。如果你从任务的角度来看问题,并让一个 executor service 替你执行任务,在选择适当的执行策略方面就获得了极大的灵活性。从本质上讲, Executor Framework 所做的工作是执行, Collections Framework 所做的工作是聚合aggregation
  不仅应该尽量不要编写自己的工作队列,而且还应该尽量不直接使用线程。当直接使用线程时, Thread 是既充当工作单元,又是执行机制。在 Executor Framework 中工作单元和执行机制是分开的。现在关键的抽象是工作单元称作任务task 。任务有两种Runnable 及其近亲 Callable (它与 Runnable 类似,但它会返回值,并且能够抛出任意的异常)。执行任务的通用机制是 executor service 。如果你从任务的角度来看问题,并让一个 executor service 替你执行任务,在选择适当的执行策略方面就获得了极大的灵活性。从本质上讲, Executor Framework 所做的工作是执行, Collections Framework 所做的工作是聚合aggregation
  在 Java 7 中, Executor Framework 得到了扩展,它可以支持 fork-join 任务了,这些任务是通过一种称作 fork-join 池的特殊 executor 服务运行的。fork-join 任务用 ForkJoinTask 实例表示,可以被分成更小的子任务,包含 ForkJoinPool 的线程不仅要处理这些任务,还要从另一个线程中“偷”任务,以确保所有的线程保持忙碌,从而提高 CPU 使用率、提高吞吐量并降低延迟。fork-join 任务的编写和调优是很有技巧的。并发的 stream (详见第 48 条)是在 fork join 池上编写的,我们不费什么力气就能享受到它们的性能优势,前提是假设它们正好适用于我们手边的任务。
  Executor Framework 的完整处理方法超出了本书的讨论范围但是有兴趣的读者可以参阅《Java Concurrency in Practice 》一书Goetz06
  Executor Framework 的完整处理方法超出了本书的讨论范围但是有兴趣的读者可以参阅《Java Concurrency in Practice》一书Goetz06