第六章 任务执行
摘要: 本章主要介绍了任务的概念,一直执行的策略。
6.1 任务
1. 任务的概念
什么是任务? 其实大多是并发编程应用都是围绕"任务执行(Task excution)" 来构造的。任务通常是一些抽象且离散的工作单元,将应用程序的工作分解到多个任务中,可以简化程序的组织结构,提供一种自然的事务边界来优化错误恢复过程,以及提供一种自然的并行工作结构来提升并发性。
2. 什么是任务边界
任务边界通常理解为任务之间是相互独立的,任务并不依赖于其他任务的的状态、结果或边界效应。
3. 任务调用策略
书中第一节给出了两个不是很好的策略。第一个是串行执行,简单的就是同一时间只能执行一个任务其他任务阻塞。第二个就是显示的创建线程,以web服务器为例,每当有一个socket连接的时候就new thread去处理。
第一个的缺点不言而喻,阻塞就会导致吞吐率大幅降低,这显然是不合适的。第二个的缺点在于首先对于操作系统而言创建一个线程开销是很高的,而且大量的无休止的创建线程对资源的消耗是巨大的,因为大多数的时间浪费在资源的争夺上,最后就是不同的平台对于线程数量的支持是不同的,那么这样的代码的稳定性就差很多。
6.2 Executor 框架
在总结了第一节的两个方法后,本章给出了一个比较好的解决办法就是使用Java.util.concurrent的executor框架,这个executor是一个接口,其中只有一个抽象方法就是void execute(Runable command)方法。
Executor可以通过多个Java开发者提供的工厂方法获取,常用的包括newFixedThreadPool、newCacheThreadPool等等。一般的原理都是先生成一些线程,当任务来临时分配一个线程,若发生未预期的Exception的话则重新创建一个新的线程补充进去。
1. Executor 的生命周期
ExecutorService接口拓展了Executor接口,提供了isShotDown()等方法用来完善Executor的生命周期。ExecutorService的生命周期有三种: 运行、关闭、已终止。
6.3 找出可利用的并行性
任务执行并不一定只局限于多个请求,一个请求也可以发掘出他的并行性然后使用Executor去完成任务。在上述Executor框架中,他接收Runable为调用通常会有一些局限性,本章推荐使用Callable来调用任务并且获取Future来查看结果。他们的关系请看: Callable与Runable。
总结: 在日常中我们可能很少真正的用到上述的东西,但是我认为上述的思想和框架是很重要的。就本章而言如果我们想要在应用程序分解为多任务时取得最大的好处就必须清晰的定义任务边界和任务粒度。