创建一个容量为5的线程池

ExecutorService executorService = Executors.newFixedThreadPool(5);

向线程池提交15个任务,其实就是通过线程池来启动15个线程

for(int i = 0;i<15;i++){
  executorService.execute(new TestRunnable());
  System.out.println("============ "+i);
}

每个线程执行完毕打印输出线程信息。

代码如下:

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class ThreadPool {
public static void main(String[] args) {
//创建一个容量为5的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i = 0;i<15;i++){
//向线程池提交一个任务(其实就是通过线程池来启动一个线程)
executorService.execute(new TestRunnable());
System.out.println("============ "+i);
}
} } class TestRunnable extends Thread{
@Override
public void run(){
try {
Thread.sleep(1000*1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-线程被调用了");
}
}

  但是发现所有线程都执行完毕,但是JVM并未结束运行,这是线程池并没有正常关闭。

怎么来正确关闭线程池呢,来看下面的分析:

下面文章转自简书:https://www.jianshu.com/p/75701f282af4

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

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

public static void main(String[] args) {  

    ExecutorService pool = Executors.newFixedThreadPool(5);
final long waitTime = 8 * 1000;
final long awaitTime = 5 * 1000; Runnable task1 = new Runnable(){
public void run(){
try {
System.out.println("task1 start");
Thread.sleep(waitTime);
System.out.println("task1 end");
} catch (InterruptedException e) {
System.out.println("task1 interrupted: " + e);
}
}
}; Runnable task2 = new Runnable(){
public void run(){
try {
System.out.println("task2 start");
Thread.sleep(1000);
System.out.println("task2 end");
} catch (InterruptedException e) {
System.out.println("task2 interrupted: " + e);
}
}
};
// 让学生解答某个很难的问题
pool.execute(task1); // 让学生解答很多问题
for(int i=0; i<1000; ++i){
pool.execute(task2);
} try {
// 向学生传达“问题解答完毕后请举手示意!”
pool.shutdown(); // 向学生传达“XX分之内解答不完的问题全部带回去作为课后作业!”后老师等待学生答题
// (所有的任务都结束的时候,返回TRUE)
if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
pool.shutdownNow();
}
} catch (InterruptedException e) {
// awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
System.out.println("awaitTermination interrupted: " + e);
pool.shutdownNow();
} System.out.println("end");
}

可以看出上面程序中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的时候)

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

线程池ExecutorService的使用及其正确关闭方法的更多相关文章

  1. [Java线程] Java线程池ExecutorService

    示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...

  2. 【Java线程】Java线程池ExecutorService

    示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...

  3. 多线程 线程池 ExecutorService

    package org.zln.thread; import java.util.Date; import java.util.concurrent.ExecutorService; import j ...

  4. Java中的线程池ExecutorService

    示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...

  5. (转)线程池 ExecutorService 详细介绍以及注意点区别

    线程池 ExecutorService 相信java开发都用到,这里做个简单笔记 一 Java通过Executors提供四种线程池,分别为: newCachedThreadPool创建一个可缓存线程池 ...

  6. 线程池ExecutorService的使用

    转载自: 海子 Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短 ...

  7. PAIP.并发编程 多核编程 线程池 ExecutorService的判断线程结束

    PAIP.并发编程 多核编程 线程池 ExecutorService的判断线程结束 ExecutorService并没有提供什么 isDone()或者isComplete()之类的方法. 作者Atti ...

  8. Java线程池ExecutorService和CountDownLatch的小例子

    import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java ...

  9. 线程池(ExecutorService)初体验

    背景:查询月统计数据,因为查询日统计数据功能已经实现.月统计数据,只是参一个List(date) 参数,for循环调用日统计,然后把结果整合就OK. 问题:单线程跑  太耗时间 解决方案:使用多线程, ...

随机推荐

  1. 总结下抽象类Abstract和虚方法Virtual(易混点)

    1.普通类中可以有普通方法和虚方法,在被继承时,虚方法可以选择重写(override)或者不重写 2.抽象类中可以有普通方法和虚方法和抽象方法,当被继承时 如下 1.普通方法不能被子类重写 2.虚方法 ...

  2. ansible playbook详解

    ansible playbook是由yml语法书写,结构清晰,可读性强,所以必须掌握yml基础语法 语法 描述 缩进 YAML使用固定的缩进风格表示层级结构,每个缩进由两个空格组成,不能使用tabs键 ...

  3. 一文懂SSM项目中的web.xml常用配置项

    做web后端工程师,逃不过的web.xml,我们都知道配置这个文件是日常工作了,那么我们来把一些必须知道知识点梳理下. 我们把web项目启动的时候,首先加载的就是web.xml这个文件,只有这个文件所 ...

  4. python文件读取:遇见的错误及解决办法

    问题一: TypeError: 'str' object is not callable 产生原因: 该错误TypeError: 'str' object is not callable字面上意思:就 ...

  5. 【转载】Hadoop面试(1)

    转自:http://www.cnblogs.com/xiaolong1032/p/4504992.html 列举出hadoop常用的一些InputFormat InputFormat是用来对我们的输入 ...

  6. MyBatis(7)——使用注解开发

    说明:注解就是利用接口实现的,因此转为面向接口编程,使用接口开发拓展性好.分层开发时上层不用管理具体的实现.更加标准化更加规范.使得各个层的耦合度更低. 注:有了注释语句就不需要实体类的mapper文 ...

  7. Added non-passive event listener to a scroll-blocking 'touchmove' event. Consider marking event handler as 'passive' to make the page more responsive

     Vue控制台警告:  Added non-passive event listener to a scroll-blocking 'touchmove' event. Consider markin ...

  8. 概率dp light 1321

    题意:给定一张无向图,每条边都有一个通过的概率 ,如果无法通过,那么就要回到起点重新出发从起点到终点的时间固定为K,如果成功到达,又需要额外花费K的时间,问走S次的最小期望时间 思路:这道题分为两部分 ...

  9. JQuery选择器(转载)

    原文:https://blog.csdn.net/colorapp/article/details/44279645 一. jQuery选择器是什么1. CSS选择器要是某个样式应用于特定的HTML元 ...

  10. 脚手架搭建的react中使用bootstrap

    1.在react的index.html文件中加个jQuery的引入 <script src="http://cdn.bootcss.com/jquery/3.3.1/jquery.mi ...