第八十条:与线程相比,首选执行器、任务和流

Java.util.concurrent包中的Executor Framework提供了一种灵活的任务执行工具,替代了手动创建线程和工作队列的方式。Executor服务允许优雅地终止任务,支持多种任务执行策略如线程池,包括固定线程数的线程池和缓存线程池。在高负载场景下,推荐使用固定线程数的线程池以避免过度创建线程。Executor Framework分离了任务(Runnable和Callable)与执行机制,提供更灵活的并发控制。Java 7引入的Fork-Join框架进一步提升了并行计算的性能,平行流则简化了利用Fork-Join池的编程难度。



  本书的第一版包含简单的工作队列(work queue)*的代码[Bloch01, Item 49]。这个类允许客户端将后台线程的异步处理工作排入队列。当不再需要这个工作队列时,客户端可以调用一个方法,让后台线程完成了已经在队列中的所有工作之后,优雅地终止自己。这个实现几乎就想件玩具,但即使如此,它还是需要一整页微妙、精致的代码,一不小心,就容易出现安全问题或者导致活性失败(liveness failure)。辛运的是,你不再需要编写这样的代码了。

  到本书第二版出版时,java.util.concurrent已添加到Java中。该软件包包含一个Executor Framework,它是一个灵活的基于接口的任务执行工具。创建一个比本书第一版更好的工作队列只需要一行代码:

ExecutorService exec = Executors.newSingleThreadExecutor();


  以下是如何提交runnable以供执行:

exec.execute(runnable);


  下面是告诉executor如何优雅地终止(如果你没有这样做,很可能你的VM不会退出):

exec.shutdown();


  你可以利用executor service完成更多的事情。例如,可以等待一项指定的任务【执行】完成(就项第79项,原书319页中的使用get方法一样),你可以等待一个任务集合中的任何任务或者所有任务完成(利用invokeAny或者invokeAll方法),你可以等待executor service终止(使用awaitTermination方法),你可以在完成任务时逐个检索任务结果(使用一个ExecutorCompletionService),您可以安排任务在特定时间运行或定期运行(使用一个ScheduledThreadPoolExecutor),等等。

  如果想让不止一个线程来处理来自这个队列的请求,只要调用一个不同的静态工厂,这个工厂创建了一种不同的executor service,称作线程池(thread pool)。你可以用固定或者可变数目的线程创建一个线程池。java.util.Executors类包含了静态工厂,能为你提供所需的大多数executor。然而,如果你想来点特别的,可以直接使用ThreadPoolExecutor类。这个类允许你控制线程池操作的几乎每个方面。

  为特定应用程序选择执行程序服务可能很棘手。对于小程序,或者轻载的服务器,使用Executors.newCachedThreadPool通常是个不错的选择,因为它不需要配置,并且一般情况下能够正确地完成工作。但是对于大负载的服务器来说,缓存的线程池就不是很好的选择了!在缓存的线程池中,被提交的任务没有排成队列,而是直接交给线程执行。如果没有线程可用,就创建一个新的线程。如果服务器负载得太重,导致了它所有的CPU都完全被占用了,当有更多的任务时,就会创建更多的线程,这样只会使情况变得更糟。因此,在大负载的产品服务器中,最好使用Executors.newFixedThreadPool,它为你提供了一个包含固定线程数目的线程池,或者为了最大限度地控制它,就直接使用ThreadPoolExecutor类

  您不仅应该避免编写自己的工作队列,而且通常情况下也应该避免直接使用线程。当您直接使用线程时,线程既可以作为工作单元,也可以作为执行它的机制。在executor Framework中,工作单元和执行机制是分开的。关键的抽象是工作单元,也就是任务(task)。有两种任务: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的线程不仅处理这些任务,而且还彼此“窃取(steal)”任务以确保所有线程都保持忙碌,从而导致更高的CPU利用率,更高的吞吐量和更低的延迟。编写和调优fork-join任务很棘手。Parallel流(第48项)写在fork join连接池之上,允许你轻松利用其性能优势,假设它们适合你手头上的任务。

  对Executor Framework的完整处理超出了本书的范围,但感兴趣的读者可以参考《Java Concurrency in Practice》[Goetz06]。

所有文章无条件开放,顺手点个赞不为过吧!

                                          

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值