并发编程实战(8)任务的执行与取消

单任务顺序执行与每个任务一个线程

单线程顺序执行任务,效率过低,没有发挥多核的优势。

为每个任务分配一个线程去执行,速度有所提升,但是有很大的缺陷。

无限制创建线程的不足

生产环境中,每一个任务分配一个线程的方法存在一些缺陷,尤其是需要创建大量的线程时:

  • 线程生命周期的开销非常高。线程的创建与销毁是有代价的,延迟处理的请求,并且需要JVM和操作系统提供一些辅助操作。如果请求的到达率非常高且请求的处理过程是轻量级的,例如大多数服务器应用程序就是这种情况,那么为每个请求创建一个新线程将消耗大量的计算资源。
  • 资源消耗。活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于柯用处理器的数量,那么有些线程将闲置。大量闲置的线程会占用许多内存,给垃圾回收器带来压力,并且大量线程在竞争CPU资源时还将产生其他的性能开销。如果已经拥有足够多的线程使所有CPU保持忙碌状态,那么再创建更多的线程反而会降低性能。
  • 稳定性。可创建线程的数量存在一个限制,不是无限的,跟平台有关,受多个因素制约。比如JVM启动参数、Thread构造函数中请求的栈大小以及底层操作系统对线程的限制等。破坏了限制会跑出OutOfMemory异常,很难恢复。

Executor框架

提供一种标准的方法将任务的提交过程与执行过程解耦,并用Runnable来表示任务。Executor的实现还提供了对生命周期的支持以及统计信息收集、应用程序管理机制和性能监视等机制。

Executor是基于生产者-消费者模式的,提交任务的操作相当于生产者,执行任务的线程则相当于消费者。

执行策略

在执行策略中定义了任务执行的“What、Where、When、How”等方面:

  • 在什么(What)线程中执行任务?
  • 任务按照什么(What)顺序执行(FIFO、LIFO、优先级)?
  • 有多少个(How Many)任务能够并发执行?
  • 在队列中有多少个(How Many)任务在等待执行?
  • 如果系统由于过载需要拒绝一个任务,那么应该选择哪一个(Which)任务?如何通知(How)应用程序有任务被拒绝?
  • 在执行一个任务之前或之后,应该进行哪些(What)动作?

执行策略可以看做一种资源管理工具,最佳策略取决于可用的计算资源以及对服务质量的需求。

Executor的生命周期

Executor是异步执行的,所以提交的任务不是立即可见的。有些任务可能已经完成,有些可能正在运行,其他任务可能在队列中等待执行。

Executor扩展了ExecutorService接口,添加了用于管理生命周期的方法。

  • shutdown(): 执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成,包括那些未开始执行的任务。
  • shutdownNow(): 执行粗暴的关闭过程,尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。
  • isShutdown()
  • isTerminated(): 轮询ExecutorService是否已经终止
  • awaitTermination(long timeout, TimeUnit unit): 等待ExecutorService到达终止状态。通常调用awaitTermination之后会立即调用shutdown()

延时任务与周期任务

Timer在执行所有定时任务时只会创建一个线程,如果某个任务的执行时间过长,将会破坏其他TimerTask的定时精确性。此外Timer线程不捕获异常,因此当TimerTask跑出未检查异常时将终止定时线程。这种情况下,Timer也不会恢复线程的执行,而是会错误的认为整个Timer都被取消了。因此,已经被调度但尚未执行的TimerTask将不会再执行,新的任务也不能被调度。(这个问题被称为“线程泄露”)。

调度服务可以使用延迟队列,底层实现用BlockingQueue,为ScheduledThreadPoolExecutor提供调度功能。每个Delayer对象都有一个相应的延迟时间:在DelayQueue中,只有某个元素逾期后才能从DelayQueue中执行take操作。从DelayQueue中返回的对象是根据它们的延迟时间进行排序的。

线程中断

对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己(这些时刻也被称为取消点)。

通常,中断是实现取消的最合理方式。

中断策略

中断策略规定线程如何解释某个中断请求—当发现中断请求时,应该做哪些工作,哪些工作单元对于中断来说是原子操作,以及多快的速度响应中断。

最合理的中断策略:某种形式的线程级取消操作或服务级取消操作:尽快退出,在必要时进行清理,通知某个所有者线程已经退出。

其他中断策略:暂停服务或重新开始服务,适用于包含非标准中断策略的线程或线程池。

当检查到中断请求时,任务并不需要放弃所有操作,它可以推迟处理中断请求,并直到某个更何时的时刻。因此需要记住中断请求,并在完成当前任务后抛出InterruptedException火表示已经收到中断请求。这么做能够确保在更新过程中发生中断时,数据结构不会被破坏。

线程应该只能由其所有者中断,任务代码将任务提交给线程去执行,不应该对其执行所在的线程的中断策略做出假设。由于每个线程拥有各自的中断策略,除非明确知道中断对该线程的含义,否则就不应该中断这个线程。

响应中断

只有实现了线程中断策略的代码才可以屏蔽中断请求,在常规的任务和库代码中都不应该屏蔽中断请求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值