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. Azure Virtual NetWoker(一)对等互连网络

    一,引言 Virtual NetWork Peering 可以无缝连接两个 Azure Virtual NetWork,Virtual NetWork Peering 直接的网络流量是专用的.在实际项 ...

  2. logstash的filter之grok

    logstash的filter之grokLogstash中的filter可以支持对数据进行解析过滤. grok:支持120多种内置的表达式,有一些简单常用的内容就可以使用内置的表达式进行解析 http ...

  3. Hi3516开发笔记(二):Hi3516虚拟机基础环境搭建之串口调试、网络连接以及sftp文件传输

    前言   搭建Hi3516的基础虚拟机,为交叉编译环境搭建前期工作.后续会编译一个基本的C语言程序Demo,在HI3516上跑.   虚拟机   开发本对虚拟机做了一些基本要求,如下图:    其实重 ...

  4. 带allow-create的el-select限制长度

    需求:给el-select添加新增字段长度限制且新增内容不能为空 1.首先给el-select绑定一个id(例如:selectSku),这个id会传到组件里面,绑定在那个input上面, <el ...

  5. [cf1240F]Football

    (事实上,总是可以让每一场都比,因此$w_{i}$并没有意义) 当$k=2$时,有如下做法-- 新建一个点,向所有奇度数的点连边,并对得到的图求欧拉回路,那么只需要将欧拉回路上的边交替染色,即可保证$ ...

  6. [bzoj3670]动物园

    首先计算出s数组,s表示可以重复的前缀等于后缀的个数,显然有s[i]=s[next[i]]+1,因为有且仅有next的next满足这个条件. 然后直接暴力枚举所有next,直到它小于i的一半,这个时间 ...

  7. [bzoj2257]瓶子和燃料

    先考虑选出k个后答案最小会是多少,容易发现其实就是所有的gcd(就是$ax+by=gcd(a,b)$的推广)然后相当于要最大化gcd,反过来可以将所有数的约数都打上+1标记,+1标记不少于k个且最大的 ...

  8. CKAD认证中的部署教程

    在上一章中,我们已经学会了使用 kubeadm 创建集群和加入新的节点,在本章中,将按照 CKAD 课程的方法重新部署一遍,实际上官方教程的内容不多,笔者写了两篇类似的部署方式,如果已经部署了 kub ...

  9. 从零开始学Kotlin第四课

    面向对象: //妹子 性格 声音 class Girl(var chactor:String,var voice:String) fun main(args: Array<String>) ...

  10. linux结束进程命令

    在linux中,进程之间通过信号来通信.进程的信号就是预定义好一个消息,进程能识别它并决定忽略还是做出反应. 信号 名称 描述 1 HUP 挂起 2 INT 中断 3 QUIT 结束运行 9 KILL ...