任务执行
任务是一组逻辑业务单元,通常是一些抽象的且离散的工作单元,线程则是使任务异步执行的机制。
围绕“任务执行”来设计应用程序时,首先要做的就是找出清晰的任务边界。理想情况下任务之间应该是相互独立的,且不依赖于其他任务的状态、结果和边界效应。对于大多数服务器的应用程序而言,以独立的客户请求为边界。
有2中任务执行策略:
- 串行执行。所有任务都在一个线程中,顺序执行。串行的任务执行吞吐量和响应性都很差,但是结构简单。
- 为每个任务显式的分配一个线程。但是这样做存在一些缺陷,存在资源管理的复杂性。
- 线程生命周期的开销非常高
- 资源消耗,线程会消耗系统资源,尤其是内存。
- 稳定性,不同的平台或是JVM限制了线程的数量。一定数量的线程可以提升程序的吞吐量,但是过多的线程会降低程序的效率
Executor框架
Executor将任务的提交和执行解藕开来。该框架基于生产者-消费者模式,其中任务提交的操作相当于生产者,执行任务的线程相当于消费者。
实际操作中,该框架实现了JAVA的线程池,可以管理一组同构工作线程。线程池同工作队列紧密关联,工作队列对应了任务队列。
线程池的好处在于,通过重用现有的线程而不是新创线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的开销,另外一个好处就是,当请求到达时,工作线程已经存在,而不会等待线程的创建而导致的延迟,提高了响应性。
取消与关闭
健壮的并发应用程序与看似能正常工作的应用程序之间的主要差异就是,如何实现取消和关闭操作。但是使任务或线程能安全、快速、可靠的停下来可不容易。JAVA中没有提供任何机制来安全的终止线程。只是提供了中断,这种协作机制,使一个线程可以终止另外一个线程。这种协作机制保证了,线程不会立即停止,而是清楚当前正在执行的工作,然后再停止,因为任务本身的代码更清楚如何停止线程。
任务取消
一个可取消的任务必须拥有取消策略。一般通过中断来实现取消操作。一个中断操作不会真正的中断一个正在运行的线程,它只是发出中断请求,然后由线程在下一个合适的时刻中断自己。
如果外部代码能在某个操作结束之前将其设为“完成”状态,那么这个操作就是可取消的。取消某个操作的原因有很多:
- 用户请求取消,如点击取消按钮
- 有时间限制的操作
- 应用程序事件
- 错误
- 关闭
JAVA使用中断来进行取消操作,每一个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。Thread提供了相关方法,interrupt将会中断目标线程;isInterrupted方法能返回目标线程的中断状态;interrupted将清楚当前的中断状态,并返回它之前的值,这也是清除中断状态的唯一方法。
阻塞库方法,如Thread.sleep或是Object.wait都会检测线程的中断情况,它们在响应中断时,都先会清除中断状态,抛出InterruptedException表示阻塞操作由于中断而提前结束。
对于非阻塞状态时,线程的中断状态将会被设置,然后根据将被取消的操作来检查中断状态以判断发生了中断。
中断策略
尽快退出,在必要时进行清理,通知某个所有者该线程已退出。线程只能由所有者中断,所有者可以将线程的中断策略信息封装到某个合适的取消机制中。
由于每个线程都有自己的中断策略,所以除非知道中断对于线程的意义,否则就不应该中断这个线程。
响应中断
调用可中断的阻塞函数。有2中策略处理InterruptedException
1)传递异常
2)恢复中断状态,从而使调用栈的上层代码能够对其进行处理
不支持取消但仍可调用可中断阻塞方法的操作,它们必须在循环中调用这些方法,并在发现中断后重新尝试。对于大多数可中断的阻塞方法都会在入口处检查中断状态,并且当发现该状态已被设置时会立即抛出InterruptedException。
Future
我们也可以利用Future的cancel方法来取消操作。Future是一个管理任务生命周期,处理异常以及实现取消的抽象机制。
cancel方法有一个参数mayInterruptIfRunning,从字面上我们可以理解,表示线程能否响应中断。如果为true则表示线程可以被中断;为false时,意味着如果线程没有启动那么就不要运行它,可以应用于那些不处理中断的任务中。
处理不可中断的阻塞
很多可阻塞方法可以通过提前返回或是抛出InterruptedException来响应中断,然而存在一些可阻塞方法或阻塞机制无法响应中断。
一个线程如果由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有任何其他作用。