1、什么是ExecutorService,为什么要使用线程池?

  许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务,每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁创建新线程、销毁新线程、线程切换既花费较多的时间,影响相应速度,又消耗大量的系统资源,且有时服务器无法处理过多请求导致崩溃。一种情形:假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。ExecutorService是一个线程池,请求到达时,线程已经存在,响应延迟低,多个任务复用线程,避免了线程的重复创建和销毁,并且可以规定线程数目,请求数目超过阈值时强制其等待直到有空闲线程。

  当我们有任务需要多线程来完成时,将任务(实现Runnable、callable接口、继承Thread类的对象)提交给ExecutorService。

创建方式如下:

1 public ThreadPoolExecutor(int corePoolSize,
2 int maximumPoolSize,
3 long keepAliveTime,
4 TimeUnit unit,
5 BlockingQueue<Runnable> workQueue,
6 ThreadFactory threadFactory,
7 RejectedExecutionHandler handler)

corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待有空闲线程时再执行。

maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。

keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。

unit : 时间单位,TimeUnit.SECONDS等。

workQueue : 任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

  1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,必须设置容量。此队列按 FIFO(先进先出)原则对元素进行排序。
  2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,可以设置容量,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入offer操作必须等到另一个线程调用移除poll操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  4. PriorityBlockingQueue:一个具有优先级的无限阻塞队列

threadFactory :  线程工厂,用于创建线程。

handler : 当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序。

2、ExecutorService的类型

一共有四种线程池:

CachedThreadPool可缓存线程池、SingleThreadExecutor单线程池、FixedThreadPool固定线程数线程池、ScheduledThreadPool 固定线程数,支持定时和周期性任务线程池。

ThreadPoolExecutor(该类就是线程池类)继承AbstractExecutorService类,该抽象类实现ExecutorService接口,Executors是一个工厂类,包含很多静态方法,包括newCachedThreadPool、newSingleThreadExecutor、newFixedThreadPool等,这些静态方法中调用了ThreadPoolExecutor的构造函数,并且不同的线程池调用构造方法时传入不同的参数。

2.1 CachedThreadPool可缓存线程池 (无界线程池)

1 public static ExecutorService newCachedThreadPool() {
2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
3 60L, TimeUnit.SECONDS,
4 new SynchronousQueue<Runnable>());
5 }

通过它的创建方式可以知道,创建的都是非核心线程,而且最大线程数为Interge的最大值,空闲线程存活时间是1分钟。SynchronousQueue队列,一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作。所以,当我们提交第一个任务的时候,是加入不了队列的,这就满足了,一个线程池条件“当无法加入队列的时候,且任务没有达到maxsize时,我们将新开启一个线程任务”。即当线程不够用的时候会不断创建新线程,如果线程无限增长,会导致内存溢出。所以我们的maxsize是big big。时间是60s,当一个线程没有任务执行会暂时保存60s超时时间,如果没有的新的任务的话,会从cache中remove掉。因此长时间不提交任务的CachedThreadPool不会占用系统资源。就是缓冲区为1的生产者消费者模式。

2.2 SingleThreadExecutor单线程池

1 public static ExecutorService newSingleThreadExecutor() {
2 return new FinalizableDelegatedExecutorService
3 (new ThreadPoolExecutor(1, 1,
4 0L, TimeUnit.MILLISECONDS,
5 new LinkedBlockingQueue<Runnable>()));
6 }

只用一个线程来执行任务,保证任务按FIFO顺序一个个执行。

2.3 FixedThreadPool固定线程数线程池

1 public static ExecutorService newFixedThreadPool(int nThreads) {
2 return new ThreadPoolExecutor(nThreads, nThreads,
3 0L, TimeUnit.MILLISECONDS,
4 new LinkedBlockingQueue<Runnable>());
5 }

coresize和maxmumsize相同,超时时间为0,队列用的LinkedBlockingQueue无界的FIFO队列,如果队列里面有线程任务的话就从队列里面取出线程,然后开启一个新的线程开始执行。 很明显,这个线程池始终只有size的线程在运行,大小固定,难以扩展。

2.4 ScheduledThreadPool 固定线程数,支持定时和周期性任务线程池

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}

3、创建线程的时机(线程池的工作策略)

  1. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(即如果当前运行的线程小于corePoolSize,则任务根本不会添加到workQueue中)
  2. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入工作队列,而不添加新的线程
  3. 如果无法将请求加入workQueue(但是队列已满),则创建新的线程,除非创建此线程超出 maximumPoolSize,如果超过,在这种情况下,新的任务将被拒绝。

3、代码例子

 1 public class ExecutorServicepool {
2 public static void main(String[] args) throws InterruptedException {
3 int[] a = new int[1];
4 //创建一个容量为5的线程池
5 ExecutorService executorService = Executors.newFixedThreadPool(5);
6 for(int i = 0;i<15;i++){
7 //向线程池提交一个任务(其实就是通过线程池来启动一个线程)
8 executorService.execute(new TestRunnable(a));
9        System.out.println("============ "+i);
10        Thread.sleep(millis:1000); 
         System.out.printlin("主线程休眠了1秒钟")
11 }
12 }
13 }
14
15 class TestRunnable extends Thread{
16 public int[] count;
17 TestRunnable(int[] a){
18 this.count = a;
19 }
20 @Override
21 public void run(){
22 try {
23 if(Thread.currentThread().getName().equals("pool-1-thread-1"))
24 Thread.sleep(2000);
25 } catch (Exception e) {
26 e.printStackTrace();
27 }
28 System.out.println(Thread.currentThread().getName()+"-线程被调用了");
29 System.out.println("count值为:"+(++count[0]));
30 }
31 }

得到的输出结果如下:可知,固定线程数线程池,线程数为5个,提交15个任务给线程池,也就是使用5个线程完成对a[0]++的工作,5个线程之间是异步的,线程池中线程与主线程也是异步的。

============ 0
============ 1
============ 2
============ 3
============ 4
============ 5
============ 6
============ 7
============ 8
============ 9
============ 10
============ 11
============ 12
============ 13
============ 14
pool-1-thread-3-线程被调用了
pool-1-thread-2-线程被调用了
pool-1-thread-5-线程被调用了
pool-1-thread-4-线程被调用了
count值为:2
count值为:3
count值为:1
pool-1-thread-2-线程被调用了
count值为:4
count值为:5
pool-1-thread-3-线程被调用了
pool-1-thread-2-线程被调用了
pool-1-thread-4-线程被调用了
count值为:7
count值为:6
pool-1-thread-2-线程被调用了
count值为:9
count值为:8
pool-1-thread-4-线程被调用了
count值为:10
pool-1-thread-4-线程被调用了
count值为:11
pool-1-thread-2-线程被调用了
count值为:12
pool-1-thread-5-线程被调用了
count值为:13
pool-1-thread-3-线程被调用了
count值为:14
主线程休眠了1秒钟
pool-1-thread-1-线程被调用了
count值为:15

4、怎么关闭线程池?

执行程序时发现,所有线程执行完毕后,JVM并未结束运行,也就说明线程池没有正常结束。怎样正确关闭线程池呢?

调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

让线程池在指定时间内立即关闭:

public static void main(String[] args) { 

    ExecutorService pool = Executors.newFixedThreadPool(5);
final long waitTime = 8 * 1000;
final long awaitTime = 2 * 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);
}
}
};
//消耗时间很长的任务 8秒
pool.execute(task1); //消耗时间1秒
for(int i=0; i<1000; ++i){
pool.execute(task2);
} try {
// 告诉线程池,如果所有任务执行完毕则关闭线程池
pool.shutdown(); // 判断线程池是否在限定时间内,或者线程池内线程全部结束
if(!pool.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)){
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
pool.shutdownNow();
}
} catch (InterruptedException e) {
System.out.println("awaitTermination interrupted: " + e);
} System.out.println("end");
}
task1 start
task2 start
task2 start
task2 start
task2 start
task2 end
task2 end
task2 end
task2 start
task2 end
task2 start
task2 start
task2 start
task2 end
task2 end
task2 end
task2 end
end
task2 interrupted: java.lang.InterruptedException: sleep interrupted
task2 interrupted: java.lang.InterruptedException: sleep interrupted
task1 interrupted: java.lang.InterruptedException: sleep interrupted
task2 interrupted: java.lang.InterruptedException: sleep interrupted
task2 interrupted: java.lang.InterruptedException: sleep interrupted Process finished with exit code 0

如果只执行shutdown,线程池会等待所有线程全部结束才终止线程池。

且!执行shutdown()后,就不能再继续使用ExecutorService来追加新的任务了,如果继续调用execute/submit方法执行新的任务的话,就会抛出RejectedExecutionException异常。

所以一般的调用顺序为:

shutdown 方法 ,停止接收新的任务
awaitTermination 方法, 判断任务是否执行完毕或者是否在指定时间内
shutdownNow方法

参考文献:https://www.cnblogs.com/zhncnblogs/p/10894271.html  https://blog.csdn.net/fwt336/article/details/81530581

ExecutorService 线程池详解的更多相关文章

  1. Java线程池详解(二)

    一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...

  2. 三、VIP课程:并发编程专题->01-并发编程之Executor线程池详解

    01-并发编程之Executor线程池详解 线程:什么是线程&多线程 线程:线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系 ...

  3. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  4. 【java线程系列】java线程系列之java线程池详解

    一线程池的概念及为何需要线程池: 我们知道当我们自己创建一个线程时如果该线程执行完任务后就进入死亡状态,这样如果我们需要在次使用一个线程时得重新创建一个线程,但是线程的创建是要付出一定的代价的,如果在 ...

  5. JDK自带的线程池详解

    1.线程池的使用场景 等待返回任务的结果的多步骤的处理场景, 批量并发执行任务,总耗时是单个步骤耗时最长的那个,提供整体的执行效率, 最终一致性,异步执行任务,无需等待,快速返回 2.线程池的关键参数 ...

  6. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  7. java - jdk线程池详解

    线程池参数详解 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUni ...

  8. Tomcat 连接数与线程池详解

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...

  9. java/android线程池详解

    一,简述线程池: 线程池是如何工作的:一系列任务出现后,根据自己的线程池安排任务进行. 如图: 线程池的好处: 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销. 能有效控制线程池的最大并 ...

随机推荐

  1. C#中base 和this

    [意义] this:指当前类,this调用当前类的属性,方法,包括构造函数的方法,继承本类的构造函数 base:指当前类的父类,可调用父类的非私有属性,方法,继承父类的构造函数括号里的参数 [用处] ...

  2. PLSQL批量执行SQL文件方法

    当需要执行多个sql文件,或者某个脚本中,sql语句数量很多时,手动逐个逐条执行不是一个明智的选择. PLSQL为我们提供了便捷的工具.使用方式如下: [工具]--[导入表]--[SQL插入]--[选 ...

  3. Part 37 Difference between $scope and $rootScope

    In this video we will discuss the difference between $scope and $rootScope. The main difference is t ...

  4. vue + cesium开发(4) 绘制图形

    在官方例子中每个图形都是一个entity,官方例子提供了显示正方形.圆形.锥形.图片等多种案例! // 初始花 var viewer = new Cesium.Viewer("cesiumC ...

  5. Java包装类,以及Integer与int之间的比较

    一.Java的基本类型 Java语言中提供了八种基本类型,包括六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型. 整数型,包括byte.short.int.long,默认初始值是0 ...

  6. [loj3463]表达式求值

    类似cf582E,先建出表达式树,然后树形dp+离散+min和max卷积的优化,复杂度为$o(nm|E|)$,无法通过 考虑我们仅关心于这$n$个数的大小关系,具体来说,假设给出的数组是$a_{i,j ...

  7. PHP绕过MD5比较的各种姿势

    1.用==进行弱类型比较时, 可以通过两个0e开头后面纯数字的md5绕过 php在进行弱类型比较时,如果为字符串为纯数字,包括浮点数.科学计数法.十六进制数等,都会转化为数字类型再进行比较,利用这点, ...

  8. Python实战:截图识别文字,过万使用量版本!(附源码!!)

    前人栽树后人乘凉,以不造轮子为由 使用百度的图片识字功能,实现了一个上万次使用量的脚本. 系统:win10 Python版本:python3.8.6 pycharm版本:pycharm 2021.1. ...

  9. Python技法1:变长和定长序列拆分

    Python中的任何序列(可迭代的对象)都可以通过赋值操作进行拆分,包括但不限于元组.列表.字符串.文件.迭代器.生成器等. 元组拆分 元组拆分是最为常见的一种拆分,示例如下: p = (4, 5) ...

  10. CF1361C Johnny and Megan's Necklace

    考虑\(2^x | (u \oplus v)\)的最大\(x\)小于等于\(20\) 这种题目,可以考虑搬到图上做. 我们枚举\(x\)那么对\((u\ mod\ 2^x,v\ mod\ 2^x)\) ...