06取消和关闭

取消和关闭

为什么良好的处理取消和关闭很重要?

感觉苹果就是取消机制做的好,所以用起来很舒服

任务取消

取消的含义?

在任务正常完成之前停止

何时会触发“取消”?

  1. 用户请求取消
  2. 一定时间内未完成则取消
  3. 应用程序事件:多任务搜索,其中一个搜索成功,其余的任务就取消
  4. 错误:多任务爬虫,磁盘满了,所有任务取消并记录当前状态
  5. 关闭:程序或服务停止,必须对正在执行和等待执行的工作进行取消操作。

如何理解“java中没有安全的抢占式方式来停止线程”?

抢占式方式会带来数据不一致的问题。

如何实现取消?

设置“取消”标记,定期检查

image-20200819205552641

上述实现有哪些问题?

当调用中断方法时,可能被阻塞,无法进行后续的检查。

如何解决上述问题?

中断

而对于中断方法,interrupt()可以跳出阻塞,但是需要注意的是:

中断有两个结果:

  1. 如果不是阻塞方法,仅仅将中断状态置为true
  2. 如果中断阻塞方法,将抛出InterruptException,同时中断状态置为false

中断策略

与任务有取消策略,线程也有中断策略,决定how,when,what

一般情况下,发生中断时,任务也会取消,因此通常,中断是实现取消最合理的方式。

中断时取消

中断和取消是两个概念,我们要区分”在中断发生时,顺便取消一下任务“和“使用中断实现取消任务”的区别。

中断时,任务也可以选择不取消,但是在任务完成时需要恢复中断

如何在中断时取消?

在任务中使用中断状态作为“取消”标记,用isInterrupted判断是否“取消”

中断时取消任务有两个处理:

  • 对于任务而言,由取消策略处理
  • 对于线程而言,由中断策略处理

任务和线程是两个概念,多任务也可以在单线程中执行。

在取消策略中处理了“中断状态”后,一定要将其保留,提供给中断策略处理

在任务中响应中断的方式如下:

  1. 捕获InterruptException后重新抛出非检查异常
  2. 捕获InterruptException后重新调用interrupt()恢复中断状态

任务对于线程的所有者(如线程池)如何响应中断是未知的,所以一定不能“生吞”中断

使用中断实现取消

因为我们目的是取消任务,而不是发生中断才去取消任务,那么能否直接用中断来实现任务的取消?

在中断策略前提下由中断策略决定

中断策略未知的情况下,使用中断来取消某个任务,可能会导致其它所有任务都取消了。

常见的两个可实现场景:

场景1:单线程单任务,一般用thread.interrupt()来取消

场景2:标准线程池(Exceutor框架下),配合Future的cacel()方法

取消由任务所有者发起,中断由线程所有者发起。

线程池中如何进行任务的取消?

如果是使用标准的Exceutor框架里的线程池,其中断策略是

  1. 如果池正在停止,请确保线程被中断;

  2. 如果没有,请确保线程没被中断(中断状态会恢复)。

    如果任务异常,则该线程结束,另启一个新的代替

了解到其中断策略后,可以使用中断来实现取消,最好的方式是配合Future的cacel()方法。

处理不可中断的阻塞

不可中断(innterrupt)的堵塞有:

  1. java.io包中的同步Socket I/O
  2. java.io包中的同步I/O
  3. Selector的异步I/O
  4. 获取某个锁

但是都会有对应的其他中断机制,这些情形下如何取消任务?

使用上诉的中断机制重写(在中断可以实现取消的情形下):

场景1:重写interrupt方法

场景2:重写FutureTask的cancel()

总结:

  1. 发生中断时取消任务和使用中断取消任务是两种情况
  2. 使用中断取消任务场景场景是单线程单任务和线程池配合Future(都属于中断策略已知)

停止基于线程的服务

日志服务

image-20200820192243213

存在问题:不支持取消

优化:如下

image-20200820192313356

存在问题:安全问题

优化:如下

image-20200820192345520

image-20200820192410379

关闭ExecutorService

“毒丸”对象

只执行一次的服务

shutdownNow的局限性

无法知道关闭时,哪些任务正在执行

处理非正常的线程终止

在多线程的情况下,主线程无法捕捉到其他异常信息。有如下解决方案:

  1. 当一个线程由于未捕获异常即将终止时,Java虚拟机将使用thread . getuncaughtexceptionhandler()查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。
  2. 如果希望任务抛出的异常做相应的处理,标准线程池中提供afterExecute()供改写
1
2
3
4
5
6
7
8
9
10
11
12
try {
task.run();
} catch (RuntimeException x) {
// 此处抛出的异常会被uncaughtException处理
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}

线程池中只有execute()涉及上述处理,submit()会将异常保存下来,get时获取

JVM关闭

image

关闭钩子

关闭钩子有何用?

在jvm正常关闭时,执行一些关闭操作(如上述的日志服务关闭)

由于日志线程会一直执行,想正常关闭只能走其它三种方式

关闭钩子如果无法正常完成?

会陷入死循环,无法正常关闭。

如何解决关闭钩子的并发问题?

尽量将所有逻辑写在一个关闭钩子中,避免并发安问题。

守护线程

守护线程和普通线程的区别?

当一个线程退出时,JVM会检查是否存在其它非守护线程,如果不存在,则正常退出。

终结器

避免使用终结器,可以用finnally代码块和显示的close方法代替来其它资源。

本文标题:06取消和关闭

文章作者:Sun

发布时间:2020年08月22日 - 17:08

最后更新:2020年10月27日 - 10:10

原始链接:https://sunyi720.github.io/2020/08/22/Java并发编程/06取消与关闭/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。