Tips
书中的源代码地址:
注意, 书中的有些代码里方法是基于 Java 9 API 中的, 所以 JDK 最好下载 JDK 9 以上的版本.
80. EXECUTORS, TASKS, STREAMS 优于线程
本书的第一版包含一个简单工作队列的代码[Bloch01, 条目 49]. 此类允许客户端将后台线程的异步处理工作排入队列. 当不再需要工作队列时, 客户端可以调用一个方法, 要求后台线程在完成队列中已有的任何工作后正常终止自身. 实现只不过是个玩具, 但即便如此, 它还需要一整页精细, 细致的代码, 如果你没有恰到好处的话, 这种代码很容易出现安全和活性失败. 幸运的是, 没有理由再编写这种代码了.
到本书第二版出版时, java.util.concurrent 包已添加到 Java 中. 该包包含一个 Executor Framework, 它是一个灵活的基于接口的任务执行工具. 创建一个比本书第一版更好的工作队列只需要一行代码:
ExecutorService exec = Executors.newSingleThreadExecutor();
下面是如何提交一个可运行的 (runnable) 执行:
exec.execute(runnable);
下面是如何告诉 executor 优雅地终止(如果做不到这一点, 你的虚拟机很可能不会退出):
exec.shutdown();
可以使用执行器服务 (executor service) 做更多的事情. 例如, 可以等待一个特定任务完成(条目 79 中使用 get 方法, 319 页), 可以等待任何或全部任务完成的集合(使用 invokeAny 或 invokeAll 方法), 也可以等待执行者服务终止(使用 awaitTermination 方法), 可以在完成任务时逐个检索任务结果(使用 ExecutorCompletionService), 可以安排任务在特定时间运行或定期运行(使用 ScheduledThreadPoolExecutor), 等等.
如果希望多个线程处理来自队列的请求, 只需调用另一个静态工厂, 该工厂创建一种称为线程池的不同类型的执行器服务. 可以创建具有固定或可变数量线程的线程池. java.util.concurrent.Executors 类包含静态工厂, 它们提供了你需要的大多数执行程序. 但是, 如果想要一些与众不同的东西, 可以直接使用 ThreadPoolExecutor 类. 此类允许你配置线程池操作的几乎每个方面.
为特定应用程序选择执行程序服务可能很棘手. 对于小程序或负载较轻的服务器, Executors.newCachedThreadPool 通常是一个不错的选择, 因为它不需要配置, 通常 "做正确的事情". 但是对于负载很重的生产服务器来说, 缓存线程池不是一个好的选择! 在缓存线程池中, 提交的任务不会排队, 而是立即传递给线程执行. 如果没有可用的线程, 则创建一个新线程. 如果服务器负载过重以至于所有 CPU 都被充分利用并且更多任务到达时, 则会创建更多线程, 这只会使事情变得更糟. 因此, 在负载很重的生产服务器中, 最好使用 Executors.newFixedThreadPool, 它提供具有固定线程数的池, 或直接使用 ThreadPoolExecutor 类, 以实现最大程度的控制.
不仅应该避免编写自己的工作队列, 而且通常应该避免直接使用线程. 当直接使用 Thread 类时, 线程既可以作为工作单元, 也可以作为执行它的机制. 在 executor framework 中, 工作单元和执行机制是分开的. 关键的抽象是工作单元, 称为任务. 有两种任务: Runnable 及其近亲 Callable(类似于 Runnable, 除了它返回一个值并且可以抛出任意异常). 执行任务的一般机制是 executor service. 如果从任务的角度来看, 让 executor service 为你执行它们, 可以灵活地选择适当的执行策略以满足你的需求, 并在需求发生变化时更改策略. 本质上本质上, Executor Framework 执行的功能与 Collections Framework 聚合 (aggregation) 功能是相同的.
在 Java 7 中, Executor Framework 被扩展为支持 fork-join 任务, 这些任务由称为 fork-join 池的特殊 executor service 运行. 由 ForkJoinTask 实例表示的 fork-join 任务可以拆分为较小的子任务, 而包含 ForkJoinPool 的线程不仅处理这些任务, 而且还 "彼此" 窃取 "任务" 以确保所有线程都保持忙碌, 从而导致更高的任务 CPU 利用率, 更高的吞吐量和更低的延迟. 编写和调优 fork-join 任务很棘手. 并行流 (Parallel streams)(条目 48) 是在 fork-join 池之上编写的, 假设它们适合当前的任务, 那么你可以轻松地利用它们的性能优势.
对 Executor Framework 的完整处理超出了本书的范围, 但感兴趣的读者可以参考 《Java Concurrency in Practice》一书[Goetz06].
来源: https://www.cnblogs.com/IcanFixIt/p/10637783.html