https://blog.csdn.net/zaozi/article/details/38854561

https://blog.csdn.net/z69183787/article/details/48683965

前言

最近在使用ExecutorService的时候,对于与ExecutorService相关的概念有些迷糊,

加上本身ExecutorService内部的有些方法名在取名上也容易让使用者误解,导致

犯了一些错误。在解决的过程中,偶尔看到了日本人写的一篇文章简单明了,通俗易懂

所以想着翻译成中文希望能够帮助到与我有一样困惑的程序员朋友们。

原文地址如下:

http://gurimmer.lolipop.jp/daihakken/2012/01/27/javaexecutorserviceの正しい終了shutdownの仕方/

闲话少说,文章如下

虽然使用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等会抛出异常的方法)

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

  1.  
    public static void main(String[] args) {
  2.  
     
  3.  
    ExecutorService pool = Executors.newFixedThreadPool(5);
  4.  
    final long waitTime = 8 * 1000;
  5.  
    final long awaitTime = 5 * 1000;
  6.  
     
  7.  
    Runnable task1 = new Runnable(){
  8.  
    public void run(){
  9.  
    try {
  10.  
    System.out.println("task1 start");
  11.  
    Thread.sleep(waitTime);
  12.  
    System.out.println("task1 end");
  13.  
    } catch (InterruptedException e) {
  14.  
    System.out.println("task1 interrupted: " + e);
  15.  
    }
  16.  
    }
  17.  
    };
  18.  
     
  19.  
    Runnable task2 = new Runnable(){
  20.  
    public void run(){
  21.  
    try {
  22.  
    System.out.println(" task2 start");
  23.  
    Thread.sleep(1000);
  24.  
    System.out.println(" task2 end");
  25.  
    } catch (InterruptedException e) {
  26.  
    System.out.println("task2 interrupted: " + e);
  27.  
    }
  28.  
    }
  29.  
    };
  30.  
    // 让学生解答某个很难的问题
  31.  
    pool.execute(task1);
  32.  
     
  33.  
    // 生学生解答很多问题
  34.  
    for(int i=0; i<1000; ++i){
  35.  
    pool.execute(task2);
  36.  
    }
  37.  
     
  38.  
    try {
  39.  
    // 向学生传达“问题解答完毕后请举手示意!”
  40.  
    pool.shutdown();
  41.  
     
  42.  
    // 向学生传达“XX分之内解答不完的问题全部带回去作为课后作业!”后老师等待学生答题
  43.  
    // (所有的任务都结束的时候,返回TRUE)
  44.  
    if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){
  45.  
    // 超时的时候向线程池中所有的线程发出中断(interrupted)。
  46.  
    pool.shutdownNow();
  47.  
    }
  48.  
    } catch (InterruptedException e) {
  49.  
    // awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
  50.  
    System.out.println("awaitTermination interrupted: " + e);
  51.  
    pool.shutdownNow();
  52.  
    }
  53.  
     
  54.  
    System.out.println("end");
  55.  
    }

可以看出上面程序中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方法的时候需要另外一些步骤。

可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。

下列方法分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务:

  1. void shutdownAndAwaitTermination(ExecutorService pool) {
  2. pool.shutdown(); // Disable new tasks from being submitted
  3. try {
  4. // Wait a while for existing tasks to terminate
  5. if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  6. pool.shutdownNow(); // Cancel currently executing tasks
  7. // Wait a while for tasks to respond to being cancelled
  8. if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  9. System.err.println("Pool did not terminate");
  10. }
  11. } catch (InterruptedException ie) {
  12. // (Re-)Cancel if current thread also interrupted
  13. pool.shutdownNow();
  14. // Preserve interrupt status
  15. Thread.currentThread().interrupt();
  16. }
  17. }

shutdown调用后,不可以再submit新的task,已经submit的将继续执行。

shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list

[翻译][Java]ExecutorService的正确关闭方法的更多相关文章

  1. 线程池 多线程运行结束后 如何关闭? ExecutorService的正确关闭方法

    前言 最近在使用ExecutorService的时候,对于与ExecutorService相关的概念有些迷糊, 加上本身ExecutorService内部的有些方法名在取名上也容易让使用者误解,导致 ...

  2. 线程池ExecutorService的使用及其正确关闭方法

    创建一个容量为5的线程池 ExecutorService executorService = Executors.newFixedThreadPool(5); 向线程池提交15个任务,其实就是通过线程 ...

  3. Java线程池的正确关闭方法,awaitTermination还不够

    问题说明 今天发现了一个问题,颠覆了我之前对关闭线程池的认识. 一直以来,我坚信用shutdown + awaitTermination关闭线程池是最标准的方式. 不过,这次遇到的问题是,子线程用到B ...

  4. Java流的正确关闭方式

    因为流是无论如何一定要关闭的,所以要写在finally里.如下: BufferedReader reader = null; try { reader = (BufferedReader) getRe ...

  5. java的不正确使用方法以及什么情况不能使用java

    一.Python3.6新特性 什么情况下不能运用 Java 泛型   1. 前语 Java 1.5 引入了泛型来保证类型安全,避免在运行时发作类型转换反常,让类型参数化,提高了代码的可读性和重用率.可 ...

  6. eclipse上一次没有正确关闭,导致启动的时候卡死错误解决方法

    关于 eclipse启动卡死的问题(eclipse上一次没有正确关闭,导致启动的时候卡死错误解决方法),自己常用的解决方法: 方案一(推荐使用,如果没有这个文件,就使用方案二): 到<works ...

  7. 【转】线程池体系介绍及从阿里Java开发手册学习线程池的正确创建方法

    jdk1.7中java.util.concurrent.Executor线程池体系介绍 java.util.concurrent.Executor : 负责线程的使用与调度的根接口  |–Execut ...

  8. Java 小技巧和在Java避免NullPonintException的最佳方法(翻译)

                前几天就g+里面看到有人引用这篇博文.看了一下.受益颇多. 所以翻译过来,希望和大家一起学习.本人英语水平有限,假设有错,请大家指正. 原文地址(须要翻墙):http://ja ...

  9. JUC学习笔记--从阿里Java开发手册学习线程池的正确创建方法

    前言 最近看阿里的 Java开发手册,上面有线程池的一个建议: [强制]线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式, 这样的处理方式让写的同学 ...

随机推荐

  1. numpy数组及处理:效率对比

    def Sum(n): #定义一个函数(注意:格式对齐,否则会出错) a=list(range(n)) b=list(range(0,50000*n,5)) c=[] for i in range(l ...

  2. mybatis中<foreach>标签的使用

    标签太多,记一下不是特别常用的标签~留着脑袋瓜不机灵的时候看! <foreach>标签  该标签的作用是遍历集合类型的条件 <select id="countByUserL ...

  3. volatile 与 JVM 指令重排序

    前言: 在做单例模式时 有博客在评论区 推荐使用 volatile 关键字 进行修饰 然后用了两天时间查资料看文档 发现涉及的面太广 虽然已经了解为什么要使用 volatile + synchroni ...

  4. 如何使用ESP8266、ESP8285做一个WiFi中继(WiFi信号放大器)

    准备工作 准备一个深圳四博智联科技有限公司的ESP-F 模组.或者四博智联科技的NODEMCU 当我们拿到ESP-F模块后,可以按照以下接线进行测试: 即 VCC.EN 接 3.3v.GPIO15 G ...

  5. 保存eclipse个人配置的几种方式

    本人用eclipse做Java开发之前,往往要设置下eclipse的背景,字体,颜色,以便保护眼睛.但这些数据是放在workspaces里的,一旦新建workspace所有要重新调整,很麻烦,于是尝试 ...

  6. cv2.findContours

    Layout of the output array image is incompatible with cv::Mat (step[ndims-1] != elemsize or step[1] ...

  7. C++中的tolower()函数与toupper()函数

    https://blog.csdn.net/weixin_41053564/article/details/81349353 在C++语言中tolower()函数是把字符串都转化为小写字母 toupp ...

  8. RabbitMQ中,exchange1绑定exchange2,exchange1和exchange2都绑定queue1,此时消息发送给exchange1,queue1中有几条消息

    如题: 存在两个交换器 exchange1,exchange2 存在一个队列 queue1 存在三个绑定关系:exchange1绑定exchange2 ,exchange1绑定queue1,excha ...

  9. HttpInvoker客户端动态调用Demo

    private static <T> T getHttpInvokerService(String serverUrl, Class<T> serviceInterface) ...

  10. 模糊查询出list

    <resultMap id="shipList" type="com.sinochem.yunlian.yunliantraffic.domain.Ship&quo ...