线程池中断 任务如何处理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池中断 任务如何处理相关的知识,希望对你有一定的参考价值。

参考技术A 虽然使用ExecutorService可以让线程处理变的很简单,
可是有没有人觉得在结束线程运行时候只调用shutdown方法就可以了?
实际上,只调用shutdown方法的是不够的。

我们用学校的老师和学生的关系来说明这个问题。

shutdown只是起到通知的作用

我们来假设如下场景:
学校里在课上老师出了一些问题安排全班同学进行解答并对学生说“开问题解答完毕后请举手示意!”
如果有学生解答完毕后会举手对老师说“老师我做完了!”,如果大家都解题完毕后上课结束。

上面的场景对应于ExecutorService里的方法的话是下面的样子。
老师: ExecutorService
学生: ExecutorService里的线程
问题: 通过参数传递给ExecutorService.execute的任务(Runnable)
授课: main线程
学校: Java进程

“问题解答完毕后请举手示意!”是shutdown方法。“老师我做完了!”是各个任务(Runnable)的运行结束。
所有的任务(Runnable)都结束了的话main线程(授课)也结束了。

在这里,我们假设试卷中有难度较大的问题,当然学生解答较难的问题也会比较花时间。
在上面的场景中老师除了shutdown方法之外什么也做不了,只能呆呆得等着学生们说,“老师我做完了!”之后才可以有下一步动作。
这都是因为shutdown方法只是用来通知的方法。

这时如果即使授课时间结束(main线程结束),学校也不能放学(Java进程结束),因为学生们还在解题中呢。这个时候如果你是老师你会怎么做?

一般的情况肯定是经过一定的时间在授课快要结束的时候,如果还有人没有解答出来的话,或者公布给大家解题方法,

或者作为课后习题让学生回去继续思考,然后结束上课对不对!

定好下课时间后等待结束

如果经过了一定的时间任务(Runnable)还不结束的时候我们可以通过中止任务(Runnable)的执行,以防止一直等待任务的结束。
awaitTermination方法正是可以实现这个中止作用的角色。

具体的使用方法是,在shutdown方法调用后,接着调用awaitTermination方法。这时只需要等待awaitTermination方法里第一个参数指定的时间。
如果在指定的时间内所有的任务都结束的时候,返回true,反之返回false。返回false意味着课程结束的时候还有题目没有解答出来的学生。

通过shutdownNow方法,我们可以作为老师向同学发出“没有解答出来的同学明天给出解答”的命令后结束授课。

shutdownNow方法的作用是向所有执行中的线程发出interrupted以中止线程的运行。这时,各个线程会抛出InterruptedException异常(前提是
线程中运行了sleep等会抛出异常的方法)

所以正确的中止线程的方法如下:

[java] view plain copy
public static void main(String[] args)



可以看出上面程序中waitTime的值比awaitTime大的情况下,发生Timeout然后执行中的线程会中止执行而结束。
反过来如果缩小waitTime的值,增大awaitTime的值的的话,各个线程就会不被中止的正常运行至结束。

在这里,如果我们把awaitTime和shutdownNow方法全部屏蔽掉的只留下shutdown方法的话会怎样呢?

会变成表示main方法结束的「end」显示出来之后,会打印出很多的task2的start和end。
这就是虽然课程结束了,但是学校仍然不能放学的不正常状态。最恶劣的情况会导致JAVA进程一直残留在OS中。

所以我们一定不要忘记使用awaitTermination和shutdownNow

shutdown也是很重要的

看了上面的描述后可能有些人会认为,只需要执行awaitTermination和shutdownNow就可以正常结束线程池中的线程了。其实不然。
shutdown方法还有「大家只解答我要求的问题,其它的不用多做」的意思在里面。

shutdown方法调用后,就不能再继续使用ExecutorService来追加新的任务了,如果继续调用execute方法执行新的任务的话

就会抛出RejectedExecutionException异常。(submit方法也会抛出上述异常)

而且,awaitTermination方法也不是在它被调用的时间点上简单得等待任务结束而是在awaitTermination方法调用后,

持续监视各个任务的状态以或者是否线程已经运行结束。所以不调用shutdown方法执行调用awaitTermination的话由于追加出来的任务可能

会导致任务状态监视出现偏差而发生预料之外的awaitTermination的Timeout异常

正确的调用顺序是

shutdown方法
awaitTermination方法
shutdownNow方法(发生异常或者是Timeout的时候)

实际开发的系统可能会有不能强制线程中止执行的场景出现,所以虽然推荐使用上面说的调用顺序但也并不是绝对一成不变的。

另外,可以经过一定时间间隔而有计划调用任务执行的ScheduledExecutorService同样适用于上面说的调用顺序,但是在使用scheduled方法的时候需要另外一些步骤。

如何处理瞬态类加载错误,例如线程中断

【中文标题】如何处理瞬态类加载错误,例如线程中断【英文标题】:How to handle transient Class Loading error such as thread interrupt 【发布时间】:2019-11-13 22:25:30 【问题描述】:

所以我有一个ClassLoader 后代,它通过网络加载所需的类。如何正确处理瞬态错误(如临时网络中断)或线程中断超时?

这里的问题是,我唯一能从loadClass() 中扔掉的是ClassNotFoundException。一旦抛出 CNFE,JVM 似乎不会重新尝试加载类,而是会从 java 代码中触发 NoClassDefFoundError

这是合乎逻辑的,但不考虑暂时性错误。如果我们从类加载器中抛出 CNFE,则意味着该类将永远不可用并且代码将永远无法正常工作,即使问题已解决并且可以在重试时加载类。类加载代码的长时间等待是不可取的,而且并非总是可能的。

更糟糕的是,类加载器可能会被中断。它会在网络操作等待时导致 InterruptedException,这反过来又需要抛出 CNFE,再次导致无法使用的状态,即没有加载完美的类并且永远不会加载并且代码被破坏。

这里有关于如何处理瞬态类加载失败的推荐解决方案吗?我更希望代码获得一次ClassNoDefFoundError,但下次需要时重试。

基本上我有两个想法:

我们可以将类加载器标记为坏,一旦它出现瞬时错误,逐步停止使用并创建一个新的,它可能会重新尝试加载此类。 (甚至更不完整)我们可以从线程中清除中断标志并等待类加载完毕,然后恢复线程上的中断标志。

也许我什至错了,JVM 通常不应该抛出 NoClassDefFoundError 而不每次都去类加载器?

【问题讨论】:

【参考方案1】:

这里的问题是,我唯一能从 loadClass() 中抛出的东西是 ClassNotFoundException

您是否尝试过抛出一些未经检查的异常?想知道 VM 将如何处理它。

我希望代码获得一次ClassNoDefFoundError,但有一个 下次需要时重试

因此,只要其他线程重试该类,由于ClassDefNotFoundError(注意Error 后缀...),任意(数量)线程在任意点中止就可以了稍后加载?

似乎ClassDefNotFoundError(也顾名思义)被认为是不可恢复的,这对大多数应用程序来说都是有意义的。

【讨论】:

好吧,当您有运行任务的应用程序时,错误将终止一项任务,但不会导致整个应用程序关闭。在大型系统中,ClassDefNotFoundError 绝对不是一个炫技。 @alamar 实际上,它不是一个显示停止器,如果应用程序准备处理这些异常。在这些情况下,通常会显式地Class.forName(...),...)加载类,并且会适当地处理此失败。但是由于某些类加载错误基本上可能发生在代码中的任何地方,因此普通应用程序不准备在理论上可能发生的每个点尝试从该错误中恢复。 嗯,你通常在堆栈底部有一个大的捕获网,它将处理此类错误并将整个任务排队等待后续重试或失败。 Class.forName() 的问题是它的大部分依赖都是延迟加载的,它们仍然可以在任何地方拍摄。

以上是关于线程池中断 任务如何处理的主要内容,如果未能解决你的问题,请参考以下文章

面试官:线程池中线程抛了异常,该如何处理?

如何处理线程池达到服务器连接限制

线程池中某个线程执行有异常,该如何处理?

线程池中某个线程执行有异常,该如何处理?

PHP 中如何处理并发请求(使用线程、线程池或子进程)

线程池异常如何处理你都了解吗?