JDK自带的线程池详解
1、线程池的使用场景
等待返回任务的结果的多步骤的处理场景, 批量并发执行任务,总耗时是单个步骤耗时最长的那个,提供整体的执行效率,
最终一致性,异步执行任务,无需等待,快速返回
2、线程池的关键参数说明
一般情况下我们是通过ThreadPoolExecutor来构造我们的线程池对象的。
* 阿里巴巴的开发规范文档是禁止直接使用Executors静态工厂类来创建线程池的,原因是
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
(1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
(2) CachedThreadPool 和 ScheduledThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
参数说明:
corePoolSize:核心线程数,线程池最低的线程数
maximumPoolSize:允许的最大的线程数
keepAliveTime:当前线程数超过corePoolSize的时候,空闲线程保留的时间
unit: keepAliveTime线程保留的时间的单位
workQueue: 任务缓冲区
threadFactory: 线程的构造工厂
handler: 线程池饱和时候的处理策略
3、线程池的分类
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
3.1、newCachedThreadPool
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}
它是一个可以无限扩大的线程池;
它比较适合处理执行时间比较小的任务;
corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
3.2、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
它是一种固定大小的线程池;corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;
由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
3.3、ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
定时任务的使用
3.4、SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
它只会创建一条工作线程处理任务;
采用的阻塞队列为LinkedBlockingQueue;
3.5、总结
| 线程池 | 特点 | 建议使用场景 |
|---|---|---|
| newCachedThreadPool | 1、线程数无上限 2、空闲线程存活60s 3、阻塞队列 |
1、任务执行时间短 2、任务要求响应时间短 |
| newFixedThreadPool | 1、线程数固定 2、无界队列 |
1、任务比较平缓 2、控制最大的线程数 |
| newScheduledThreadPool | 核心线程数量固定、非核心线程数量无限制(闲置时马上回收) | 执行定时 / 周期性 任务 |
| newSingleThreadExecutor | 只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题) | 不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等 |
4、使用线程池容易出现的问题
| 现象 | 原因 |
|---|---|
| 整个系统影响缓慢,大部分504 | 1、为设置最大的线程数,任务积压过多,线程数用尽 |
| oom | 1、队列无界或者size设置过大 |
| 使用线程池对效率并没有明显的提升 | 1、线程池的参数设置过小,线程数过小或者队列过小,或者是服务器的cpu核数太低 |
5、线程池的监控
5.1、为什么要对线程池进行监控
线程池中线程数和队列的类型及长度对线程会造成很大的影响,而且会争夺系统稀有资源,线程数。设置不当,或是没有最大的利用系统资源,提高系统的整体运行效率,或是导致整个系统的故障。典型的场景是线程数被占满,其他的请求无响应。或是任务积压过多,直接oom
方便的排查线程中的故障以及优化线程池的使用
5.2、监控的原理
另起一个定时单线程数的线程池newSingleThreadScheduledExecutor
调用scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)定时执行监控任务;
定时任务内 通过ThreadPoolExecutor对象获取监控的对象信息,比如t线程池需要执行的任务数、线程池在运行过程中已完成的任务数、曾经创建过的最大线程数、线程池里的线程数量、线程池里活跃的线程数量、当前排队线程数
根据预设的日志或报警策略,进行规则控制
5.3、实现的细节
定义线程池并启动监控
/**
* 定义线程池的队列的长度
*/
private final Integer queueSize = 1000;
/**
* 定义一个定长的线程池
*/
private ExecutorService executorService;
@PostConstruct
private void initExecutorService() {
log.info(
"executorService init with param: threadcount:{} ,queuesize:{}",
systemConfig.getThreadCount(),
systemConfig.getThreadQueueSize());
executorService =
new ThreadPoolExecutor(
systemConfig.getThreadCount(),
systemConfig.getThreadCount(),
0,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(systemConfig.getThreadQueueSize()),
new BasicThreadFactory.Builder()
.namingPattern("async-sign-thread-%d")
.build(),
(r, executor) -> log.error("the async executor pool is full!!"));
/** 启动线程池的监控 */
ThreadPoolMonitoring threadPoolMonitoring = new ThreadPoolMonitoring();
threadPoolMonitoring.init();
}
线程池的监控
/**
* 功能说明:线程池监控
*
* @params
* @return <br>
* 修改历史<br>
* [2019年06月14日 10:20:10 10:20] 创建方法by fengqingyang
*/
public class ThreadPoolMonitoring {
/** 用于周期性监控线程池的运行状态 */
private final ScheduledExecutorService scheduledExecutorService =
Executors.newSingleThreadScheduledExecutor(
new BasicThreadFactory.Builder()
.namingPattern("async thread executor monitor")
.build());
/**
* 功能说明:自动运行监控
*
* @return <br>
* 修改历史<br>
* [2019年06月14日 10:26:51 10:26] 创建方法by fengqingyang
* @params
*/
public void init() {
scheduledExecutorService.scheduleAtFixedRate(
() -> {
try {
ThreadPoolExecutor threadPoolExecutor =
(ThreadPoolExecutor) executorService;
/** 线程池需要执行的任务数 */
long taskCount = threadPoolExecutor.getTaskCount();
/** 线程池在运行过程中已完成的任务数 */
long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
/** 曾经创建过的最大线程数 */
long largestPoolSize = threadPoolExecutor.getLargestPoolSize();
/** 线程池里的线程数量 */
long poolSize = threadPoolExecutor.getPoolSize();
/** 线程池里活跃的线程数量 */
long activeCount = threadPoolExecutor.getActiveCount();
/** 当前排队线程数 */
int queueSize = threadPoolExecutor.getQueue().size();
log.info(
"async-executor monitor. taskCount:{}, completedTaskCount:{}, largestPoolSize:{}, poolSize:{}, activeCount:{},queueSize:{}",
taskCount,
completedTaskCount,
largestPoolSize,
poolSize,
activeCount,
queueSize);
/** 超过阀值的80%报警 */
if (activeCount >= systemConfig.getThreadCount() * 0.8) {
log.error(
"async-executor monitor. taskCount:{}, completedTaskCount:{}, largestPoolSize:{}, poolSize:{}, activeCount:{},queueSize:{}",
taskCount,
completedTaskCount,
largestPoolSize,
poolSize,
activeCount,
queueSize);
;
}
} catch (Exception ex) {
log.error("ThreadPoolMonitoring service error,{}", ex.getMessage());
}
},
0,
30,
TimeUnit.SECONDS);
}
}
6、需要注意的事项
线程数要合理设置,一般建议值是核数的2倍。
线程池队列的类型和长度要根据业特性合理设置
不同的业务需要线程池隔离,避免相互影响
未每个线程池增加特有的命名规范以及关键的日志,方便出问题排查和优化
JDK自带的线程池详解的更多相关文章
- Java线程池详解(二)
一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...
- nginx源码分析线程池详解
nginx源码分析线程池详解 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...
- 三、VIP课程:并发编程专题->01-并发编程之Executor线程池详解
01-并发编程之Executor线程池详解 线程:什么是线程&多线程 线程:线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系 ...
- java - jdk线程池详解
线程池参数详解 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUni ...
- Tomcat 连接数与线程池详解
前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- Java高并发之线程池详解
线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升. ...
- Java线程池 详解(图解)
来源:www.jianshu.com/p/098819be088c 拓展: 手动创建 new ThreadPoolExecutor 的使用: https://segmentfault.com/a/11 ...
- 使用jdk自带的线程池。加载10个线程。
在开发中使用线程,经常不经意间就new Thread()一个出来,然后发现,这样做不是很好,特别是很多线程同时处理的时候,会出现CPU被用光导致机器假死,线程运行完成自动销毁后,又复活的情况. 所以在 ...
随机推荐
- Python学习笔记(三)- SyntaxError: Non-ASCII character '\xe7' in file
在编辑Python时,当有中文输出或者注释时,出现错误提示:“SyntaxError: Non-ASCII character '\xe7' in file“ 原因:python的默认编码文件是用的A ...
- Java中使用Redis的几种数据类型总结
1.String,最基本的类型 方法 set.get 2.hash redis 127.0.0.1:6379> HMSET user:1 username redis.net.cn passw ...
- leetcode 142. 环形链表 II(c++)
给定一个链表,返回链表开始入环的第一个节点. 如果链表无环,则返回 null. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 - ...
- 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_02 泛型_5_定义和使用含有泛型的接口
定义泛型接口 Scanner的接口 接口的实现类.实现这个接口,规定数据类型为String类型 ArrayList是List接口的实现类 再看下List接口的源码 泛型实现类也定义为泛型 重写泛型的方 ...
- 监控java的进程启动情况(bat)
最近有个项目需要检测某个软件崩溃重启的间隔和重启时间,百度了一下,按照自己的需求做了相应的修改 @echo off rem 定义需监控程序的进程名和程序路径,可根据需要进行修改 set AppName ...
- Vue2.0---将页面中表格数据导出excel
这不是教程,是随笔. 项目中将后台返回的数据v-for到表格中,然后需要将这个表格导出为EXCEL 只说怎么做. 一.需要安装三个依赖: npm install -S file-saver xlsx ...
- c# 对象相等性和同一性
一:对象相等性和同一性 System.Object提供了名为Equals的虚方法,作用是在两个对象包含相同值的前提下返回true,内部实现 public class Object { public v ...
- CentOS 添加硬盘创建并挂载分区
分区工具介绍: fdisk 创建MBR分区:所支持的最大卷:2T,而且对分区有限制:最多4个主分区或3个主分区加一个扩展分区 gdisk 创建GPT分区:突破MBR 4个主分区限制,每个磁盘最多支持1 ...
- Maven-maven插件(1)添加主类信息到MANIFEST.MF
1.以前面的HelloWorld项目为例,在pom.xml中添加如下代码,指定插件 <build> <plugins> <plugin> <groupId&g ...
- JDK中主要包的介绍
java.lang——包含一些Java语言的核心类,如String.Math.Integer.System和Thread,提供常用功能.java.net——包含执行与网络相关的操作的类和接口.java ...