高并发中,那些不得不说的线程池与ThreadPoolExecutor类
摘要:从整体上认识下线程池中最核心的类之一——ThreadPoolExecutor,关于ThreadPoolExecutor的底层原理和源码实现,以及线程池中的其他技术细节的底层原理和源码实现。
本文分享自华为云社区《高并发之——不得不说的线程池与ThreadPoolExecutor类浅析》,作者: 冰 河 。
既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈。。。
说起中的线程池技术,在很多框架和异步处理中间件中都有涉及,而且性能经受起了长久的考验。可以这样说,Java的线程池技术是Java最核心的技术之一,在Java的高并发领域中,Java的线程池技术是一个永远绕不开的话题。既然Java的线程池技术这么重要(怎么能说是这么重要呢?那是相当的重要,那家伙老重要了,哈哈哈),那么,本文我们就来简单的说下线程池与ThreadPoolExecutor类。
一、Thread直接创建线程的弊端
(1)每次new Thread新建对象,性能差。
(2)线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
(3)缺少更多的功能,如更多执行、定期执行、线程中断。
(4)其他弊端,大家自行脑补,多动脑,没坏处,哈哈。
二、线程池的好处
(1)重用存在的线程,减少对象创建、消亡的开销,性能佳。
(2)可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
(3)提供定时执行、定期执行、单线程、并发数控制等功能。
(4)提供支持线程池监控的方法,可对线程池的资源进行实时监控。
(5)其他好处,大家自行脑补,多动脑,没坏处,哈哈。
三、线程池
1.线程池类结构关系
线程池中的一些接口和类的结构关系如下图所示。

后文会死磕这些接口和类的底层原理和源码。
2.创建线程池常用的类——Executors
- Executors.newCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了需要,可以灵活回收空闲线程,如果没有可回收线程,则新建线程
- Executors.newFixedThreadPool:创建一个定长的线程池,可以控制线程的最大并发数,超出的线程会在队列中等待
- Executors.newScheduledThreadPool:创建一个定长的线程池,支持定时、周期性的任务执行
- Executors.newSingleThreadExecutor: 创建一个单线程化的线程池,使用一个唯一的工作线程执行任务,保证所有任务按照指定顺序(先入先出或者优先级)执行
- Executors.newSingleThreadScheduledExecutor:创建一个单线程化的线程池,支持定时、周期性的任务执行
- Executors.newWorkStealingPool:创建一个具有并行级别的work-stealing线程池
3.线程池实例的几种状态
- Running:运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务
- Shutdown: 关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于Running状态时,调用shutdown()方法会使线程池进入该状态
- Stop: 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于Running或Shutdown状态,调用shutdownNow()方法,会使线程池进入该状态
- Tidying: 如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量为0),线程池就会进入该状态。
- Terminated: 处于Tidying状态的线程池调用terminated()方法,会使用线程池进入该状态
注意:不需要对线程池的状态做特殊的处理,线程池的状态是线程池内部根据方法自行定义和处理的。
4.合理配置线程的一些建议
(1)CPU密集型任务,就需要尽量压榨CPU,参考值可以设置为NCPU+1(CPU的数量加1)。
(2)IO密集型任务,参考值可以设置为2*NCPU(CPU数量乘以2)
四、线程池最核心的类之一——ThreadPoolExecutor
1.构造方法
ThreadPoolExecutor参数最多的构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler rejectHandler)
其他的构造方法都是调用的这个构造方法来实例化对象,可以说,我们直接分析这个方法之后,其他的构造方法我们也明白是怎么回事了!接下来,就对此构造方法进行详细的分析。
注意:为了更加深入的分析ThreadPoolExecutor类的构造方法,会适当调整参数的顺序进行解析,以便于大家更能深入的理解ThreadPoolExecutor构造方法中每个参数的作用。
上述构造方法接收如下参数进行初始化:
(1)corePoolSize:核心线程数量。
(2)maximumPoolSize:最大线程数。
(3)workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
其中,上述三个参数的关系如下所示:
- 如果运行的线程数小于corePoolSize,直接创建新线程处理任务,即使线程池中的其他线程是空闲的。
- 如果运行的线程数大于等于corePoolSize,并且小于maximumPoolSize,此时,只有当workQueue满时,才会创建新的线程处理任务。
- 如果设置的corePoolSize与maximumPoolSize相同,那么创建的线程池大小是固定的,此时,如果有新任务提交,并且workQueue没有满时,就把请求放入到workQueue中,等待空闲的线程,从workQueue中取出任务进行处理。
- 如果运行的线程数量大于maximumPoolSize,同时,workQueue已经满了,会通过拒绝策略参数rejectHandler来指定处理策略。
根据上述三个参数的配置,线程池会对任务进行如下处理方式:
当提交一个新的任务到线程池时,线程池会根据当前线程池中正在运行的线程数量来决定该任务的处理方式。处理方式总共有三种:直接切换、使用无限队列、使用有界队列。
- 直接切换常用的队列就是SynchronousQueue。
- 使用无限队列就是使用基于链表的队列,比如:LinkedBlockingQueue,如果使用这种方式,线程池中创建的最大线程数就是corePoolSize,此时maximumPoolSize不会起作用。当线程池中所有的核心线程都是运行状态时,提交新任务,就会放入等待队列中。
- 使用有界队列使用的是ArrayBlockingQueue,使用这种方式可以将线程池的最大线程数量限制为maximumPoolSize,可以降低资源的消耗。但是,这种方式使得线程池对线程的调度更困难,因为线程池和队列的容量都是有限的了。
根据上面三个参数,我们可以简单得出如何降低系统资源消耗的一些措施:
- 如果想降低系统资源的消耗,包括CPU使用率,操作系统资源的消耗,上下文环境切换的开销等,可以设置一个较大的队列容量和较小的线程池容量。这样,会降低线程处理任务的吞吐量。
- 如果提交的任务经常发生阻塞,可以考虑调用设置最大线程数的方法,重新设置线程池最大线程数。如果队列的容量设置的较小,通常需要将线程池的容量设置的大一些,这样,CPU的使用率会高些。如果线程池的容量设置的过大,并发量就会增加,则需要考虑线程调度的问题,反而可能会降低处理任务的吞吐量。
接下来,我们继续看ThreadPoolExecutor的构造方法的参数。
(4)keepAliveTime:线程没有任务执行时最多保持多久时间终止
当线程池中的线程数量大于corePoolSize时,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过了keepAliveTime就会终止。
(5)unit:keepAliveTime的时间单位
(6)threadFactory:线程工厂,用来创建线程
默认会提供一个默认的工厂来创建线程,当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称
(7)rejectHandler:拒绝处理任务时的策略
如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务。
线程池总共提供了四种策略:
- 直接抛出异常,这也是默认的策略。实现类为AbortPolicy。
- 用调用者所在的线程来执行任务。实现类为CallerRunsPolicy。
- 丢弃队列中最靠前的任务并执行当前任务。实现类为DiscardOldestPolicy。
- 直接丢弃当前任务。实现类为DiscardPolicy。
2.ThreadPoolExecutor提供的启动和停止任务的方法
(1)execute():提交任务,交给线程池执行
(2)submit():提交任务,能够返回执行结果 execute+Future
(3)shutdown():关闭线程池,等待任务都执行完
(4)shutdownNow():立即关闭线程池,不等待任务执行完
3.ThreadPoolExecutor提供的适用于监控的方法
(1)getTaskCount():线程池已执行和未执行的任务总数
(2)getCompletedTaskCount():已完成的任务数量
(3)getPoolSize():线程池当前的线程数量
(4)getCorePoolSize():线程池核心线程数
(5)getActiveCount():当前线程池中正在执行任务的线程数量
高并发中,那些不得不说的线程池与ThreadPoolExecutor类的更多相关文章
- 【高并发】不得不说的线程池与ThreadPoolExecutor类浅析
大家好,我是冰河~~ 今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题. 一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但 ...
- 高并发之——不得不说的线程池与ThreadPoolExecutor类浅析
一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈... 说起Java中的线程池技术,在很多 ...
- Java多线程高并发学习笔记(三)——深入理解线程池
线程池最核心的一个类:ThreadPoolExecutor. 看一下该类的构造器: public ThreadPoolExecutor(int paramInt1, int paramInt2, lo ...
- Java并发(二十一):线程池实现原理
一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...
- SpringBoot项目框架下ThreadPoolExecutor线程池+Queue缓冲队列实现高并发中进行下单业务
主要是自己在项目中(中小型项目) 有支付下单业务(只是办理VIP,没有涉及到商品库存),目前用户量还没有上来,目前没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲 ...
- java高并发系列 - 第32天:高并发中计数器的实现方式有哪些?
这是java高并发系列第32篇文章. java环境:jdk1.8. 本文主要内容 4种方式实现计数器功能,对比其性能 介绍LongAdder 介绍LongAccumulator 需求:一个jvm中实现 ...
- Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理
Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...
- java并发编程(十五)----(线程池)java线程池简介
好的软件设计不建议手动创建和销毁线程.线程的创建和销毁是非常耗 CPU 和内存的,因为这需要 JVM 和操作系统的参与.64位 JVM 默认线程栈是大小1 MB.这就是为什么说在请求频繁时为每个小的请 ...
随机推荐
- Hello-FPGA CoaXPress 2.0 FPGA HOST IP Core PCIe Demo User Manual
目录 1 说明 4 2 设备连接 7 3 VIVADO FPGA工程 8 4 调试说明 9 图 1‑1 资料目录 4 图 1‑2 VIVADO工程目录结构 5 图 1‑3 VS软件工程目录 5 图 1 ...
- 【scipy 基础】--图像处理
SciPy库本身是针对科学计算而不是图像处理的,只是图像处理也包含了很多数学计算,所以Scipy也提供了一个专门的模块ndimage用于图像处理. ndimage模块提供的功能包括输入/输出图像.显示 ...
- Net 高级调试之九:SOSEX 扩展命令介绍
一.介绍 今天是<Net 高级调试>的第九篇文章.这篇文章设计的内容挺多的,比如:扩展的断点支持,如何查找元数据,栈回溯,对象检查,死锁检测等等,内容挺多的.功能特别强大,使用特别方便,但 ...
- python循环之for循环
当我们想让列表中的元素一个一个打印出时,用多个print()就显得代码很繁琐了,这是我们就可以用到循环 list_1 = ['one', 'two', 'three', 'four', 'five'] ...
- PLC 和Modbus/串口设备现场总线通信及短信报警解决方案
在实现 Modbus 设备与 PROFIBUS DP 协议 PLC 通讯的同时可以在手机端实时的接收报警短信,使客户足不出户了解设备与 PLC 的状态,及时处理现场中的问题. 系统组成 Modbus ...
- Gradio-Lite: 完全在浏览器里运行的无服务器 Gradio
Gradio 是一个经常用于创建交互式机器学习应用的 Python 库.在以前按照传统方法,如果想对外分享 Gradio 应用,就需要依赖服务器设备和相关资源,而这对于自己部署的开发人员来说并不友好. ...
- java当中组件和窗口的相容问题(里面包含了这些方法的作用)
窗口也称为容器,在书中大部分称为容器,这里主要讲的是底层容器. JFrame():创建一个无标签的窗口. JFrame(String s):创建标题为s的窗口 setBounds(int a,int ...
- 在自动化测试时,Python常用的几个加密算法,你有用到吗
本文分享自华为云社区<『加密算法』| 自动化测试时基于Python常用的几个加密算法实现,你有用到吗?>,作者:虫无涯 . 写在前边 这几天做自动化测试,遇到一个问题,那就是接口的请求的密 ...
- 用pycharm创建一个django框架
用pycharm创建一个django框架 注意解释器的选择和文件路径 创建完django项目 1.自动创建了一个templates目录(先删除) 2.把settings里的 TEMPLATES = [ ...
- vue-test4 -----插槽
<template> <!-- <Main class="cccc"/> <component-a/> --> <slot-d ...