Java并发读书学习笔记(六)——取消与关闭

xiaoxiao2021-02-28  60

6.1 任务取消

如果外部代码能在某个操作正常完成之前将其置入完成状态,那么这个操作就可以称为可取消的。取消某个操作的原因很多:用户请求取消;有时间限制的操作;应用程序事件;错误;关闭。

6.1.1 中断

对中断的正确理解是:它不会真正地中断一个正在运行的线程,而只是发出中断请求。然后由线程在下一个合适的时刻中断自己(这些时刻也被称为取消点)。有些方法,例如wait,sleep,join等,将严格地处理正在请求,当它们收到中断请求或者开始执行时发现某个已被设置好的中断状态时,将抛出一个异常。设计良好的方法可以完全忽略这种请求,只要它们能使调用代码对中断请求进行某种处理,设计糟糕的方法可能会屏蔽中断请求,从而导致调用栈中的其他代码无法对中断请求做出响应。

6.1.2 中断策略

最合理的中断策略是某种形式的线程级取消操作或服务级取消操作:尽快退出,在必要时进行清理。通知某个所有者该线程已经退出。此外还可以建立其他的中断策略,例如暂停服务或重新开始服务,但对于那些包含非标准中断策略的线程或线程池,只能用于能知道这些策略的任务中。

6.1.3 响应中断

当调用可中断的阻塞函数时,有两种实用策略可用于处理InterruptedException:传递异常,从而使你的方法也成为可中断的阻塞方法;恢复中断状态,从而使调用栈中的上层代码能够对其进行处理。

6.1.4 通过Future取消

已有一种抽象机制来管理任务的生命周期,处理异常以及实现取消,即Future。通常,使用现有库中的类比自行编写的更好,因此可以使用Future和任务执行框架来构建timedRun。

6.1.5 处理不可中断的阻塞

在Java库中,许多可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的,从而使开发人员更容易构建出能响应取消请求的任务。然而,并非所有可阻塞方法或阻塞机制都能响应中断;如果一个线程由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有任务其他作用。对于那些由于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但要求我们必须知道线程阻塞的原因。

6.1.6 采用newTaskFor来封装非标准的取消

当把一个Callable提交给ExecutorService时,submit方法会返回一个Future,我们可以通过这个Future来取消任务。newTaskFor是一个工厂方法,它将创建Future来代表任务。newTaskFor还能返回一个RunnableFuture接口,该接口扩展了Future和Runnable。

6.2 停止基于线程的服务

正确的封装原则是:除非拥有某个线程,否则不能对该线程进行操控。例如,中断线程或者修改线程的优先级等。在线程API中,并没有对线程所有权给出正式的定义:线程由Thread对象表示,并且像其他对象一样可以被自由共享。然而,线程有一个相应的所有者,即创建该线程的类。因此线程池是其工作者线程的所有者,如果要中断这些线程,那么应该使用线程池。

6.2.1 关闭ExecutorService

有两种方法:使用shutdown正常关闭,已经使用shutdownNow强行关闭。在强行关闭时,shutdownNow首先关闭当前正在执行的任务,然后返回所有尚未启动的任务清单。

6.2.2 “毒丸对象”

另一种关闭生产者-消费者服务的方式就是使用“Poison Pill”对象,毒丸是指一个放在队列上的对象,其含义是:当得到这个对象时,立即停止。在FIFO队列中,毒丸对象将确保消费者在关闭之前首先完成队列中的所有工作,在提交毒丸对象之前提交的所有工作都会被处理,而生产者在提交了毒丸对象后,将不会再提交任何工作。

6.3 JVM关闭

JVM既可以正常关闭,也可以强行关闭。正常关闭触发方式有多种,包括:当最后一个非守护线程结束时,或者调用了System.exit时,或者通过其他特定于平台的方法关闭时,虽然可以通过这些标准方法来正常关闭JVM,但也可以通过调用Runtime.halt或在操作系统中杀死JVM进程来强行关闭JVM。

6.3.1 关闭钩子

在正常关闭中,JVM首先调用所有已注册的关闭钩子。关闭钩子是指通过Runtime.addShutdownHook注册但尚未开始的线程。JVM并不能保证关闭钩子的调用顺序。在关闭应用程序线程时,如果有线程仍然在运行,那么这些线程接下来将与关闭线程并发执行。当所有的关闭钩子都执行结束时,如果runFinalizersOnExit为true,那么JVM将运行终结器,然后再停止。JVM并不会停止或中断任何在关闭时仍然运行的应用程序线程。当JVM最终结束时,这些线程将被强行结束。如果关闭钩子或终结器没有执行完成,那么正常关闭进程挂起并且JVM必须被强制关闭。当被强行关闭时,只是关闭JVM,而不运行关闭钩子。

6.3.2 守护线程

普通线程和守护线程之间的差异仅在于当线程退出时发生的操作。当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出操作。当JVM停止时,所有仍然存在的守护线程都将被抛弃——既不会执行finally代码,也不会执行回卷栈,而JVM只是只是直接退出。

6.3.3 终结器

由于终结器可以在某个由JVM管理的线程中运行,因此终结器访问的任何状态都可能被多个线程访问,这样就必须对其访问操作进行同步。终结器并不能保证他们将在何时运行甚至是否会运行,并且复杂的终结器通常还会在对象上产生巨大的性能开销。要编写正确的终结器是非常困难的。在大多数情况下,通过使用finally代码块和显示的close方法,能够比使用终结器更好地管理资源。唯一的例外情况在于:当需要管理对象,并且该对象持有的资源是通过本地方法获得的。基于这些原因以及其他一些原因,我们尽量避免编写或使用包含终结器的类。

转载请注明原文地址: https://www.6miu.com/read-2350257.html

最新回复(0)