Open funnycoding opened 4 years ago
任务和线程的「启动」很容易,在大多数时候,我们会让它们正常运行直到结束或者「自行停止」。
然而,有时候我们希望提前结束任务或线程,背后的原因可能是用户进行了「取消」操作,也可能是「应用程序」需要被快速关闭。
任务和线程的「启动」很容易,在大多数时候,我们会让它们正常运行直到结束或者「自行停止」。
然而,有时候我们希望提前结束任务或线程,背后的原因可能是用户进行了「取消」操作,也可能是「应用程序」需要被快速关闭。
要使任务和线程能**「安全」**、**「快速」**、**「可靠」**地停止下来,并不是一件易事。 `Java` **没有提供任何机制**来**安全地终止线程**。(`Thread.stop` 和 `suspend` 提供了这样的机制,但是因为**严重的缺陷**,应避免使用这两个方法)。 但是 `Java` 提供了 **「中断」**(Interruption) ,这是一种 **「协作机制」**,**能够使一个线程终止另一个线程的当前工作。** `Thread.stop` 存在的缺陷: 1. **立即释放** 线程持有的监视器锁; 【立即释放监视器锁后,以前被这些锁保护的 **共享状态** 立刻 **对其他线程可见**,因为释放锁的那一刻线程执行情况未知,所以共享状态可能 **违反一致性协议**,最终结果无法预料。】 [Why Are Thread.stop, Thread.suspend,Thread.resume and Runtime.runFinalizersOnExit Deprecated](https://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html) **【↑这篇官方文档里详细描述了为什么 `Thread.stop`、`Thread.resume` 和 `Runtime.runFinalizersOnExit` 被废弃。】** 这种协作式的方法是必要的,我们很少希望某个任务、线程或服务**「立即停止」**,因为这种**立即停止**会导致「共享」的「数据结构」 处于**不一致**的状态。 相反,在编写任务和服务时可以使用一种**协作**的方式:当需要停止时,它们**首先**会**「清除当前正在执行的工作」**,**然后再结束**。 这提供更好的**灵活性**,因为「**任务本身**」的代码比发出取消请求的代码更清楚如何执行清除工作。 **生命周期结束(End-of-Lifecycle)** 的问题会使**「任务」**、**「服务**」以及程序的设计和实现等过程变得**复杂**,而这个在程序设计中非常重要的要素经常被忽略。 一个在**「行为良好」**的软件与**「勉强运行」** 的软件中**最主要的区别**就是:行为良好的软件能很完善地处理**「失败」**、**「关闭」**和**取消**等过程。 **<---【也就是对异常情况有着完备的处理机制和措施,应用程序必须对异常有着完备的处理】** 本章将给出各种实现**「取消」** 和 **「中断」** 的机制,以及如何编写任务和服务,使它们对取消请求做出相应。 **<---【点明主题】** ### 7.1 任务取消 如果外部代码能在某个操作正常完成之前将其设置为「完成」状态,那么这个操作就可以被称为是 **「可取消的」(Cancellable)**。 取消某个操作的**原因**很多: - **用户主动请求取消。**用户点击图形界面中的"取消"按钮,或者通过管理接口发来取消请求,例如 `JMX(Java Management Extensions)` - **有时间限制的操作。**某个应用程序需要在限定时间内搜索问题空间,并在这个时间内选择最佳的解决方案。当计时器超过时,需要取消所有正在搜索的任务。 - **一个应用程序事件。**例如当应用程序对某个问题空间进行分解并搜索时,不同的任务可以搜索问题空间中的不同区域【也就是并发执行】,当其中一个任务找到了解决方案时,其他扔在搜索的任务都将被取消。 - **当发生错误时。**比如爬虫程序搜索相关页面,当任务发生错误时(例如磁盘空间不足)那么其他所有搜索任务都会取消,此时可能会「记录它们当前的状态」,以便稍后重新启动。 - **应用程序关闭。** 当一个程序或服务关闭时,必须对正在处理和等待处理的工作执行某种操作。在**「平缓的关闭」**过程中,当前正在执行的任务将继续执行直到完成,而在**「立即关闭」**的过程中,当前的任务可能被取消。 在 `Java` 中没有一种**安全的**「**抢占式**」方法来停止线程,因此也就没有**「安全的抢占式方法」**来停止任务。只有一些 **「协作式」** 的机制,使**请求取消任务**和**代码**都遵循一种**协商**好的**「协议」**。 「其中一种」 协作机制能设置某个**「已请求取消(Cancellation Requested)」** 标志,而任务将定期查看该标志。当该标志位被设置为 `TRUE` 时,那么任务将**「提前结束」。** **程序清单7-1** 就使用了这个方法,其中的 `PrimeGenerator` 持续地枚举素数,直到它被取消。 `cancel` 方法将设置 `cancelled` 标志,并且主循环在每次做搜索下一个素数这个操作之前都会检查一下这个标志(为了使这个过程能**「可靠地」**工作,标志 `cancelled` 必须是 `volatile` 类型【保证变量的可见性】) > **程序清单 7-1** 使用 `volatile` 类型的域来保存取消状态: ```java // 通过一个被 volatile 修饰的标志位来判断任务是否被取消 @ThreadSafe public class PrimeGenerator implements Runnable { private static ExecutorService exec = Executors.newCachedThreadPool(); @GuardedBy("this") private final List