并发基础之线程池(Thread Pool)
目录
- 前言
- 何为线程池
- 线程池优势
- 创建线程池方式
- 直接实例化ThreadPoolExecutor类
- JUC Executors 创建线程池
- 线程池挖掘
- Executors简单介绍
- ThreadPoolExecutor核心类
- ThreadPoolExecutor 类构造参数含义
- 线程池运行规则
- 线程设置数量
- 结语
前言
相信大家都知道当前的很多系统架构都要求高并发,所谓高并发(High Concurrency)就是系统通过设计满足多个请求并行的能力,如果非要通俗一点就是系统在单位时间要满足较高的QPS\TPS。那么,如何让系统满足这些高并发能力呢?满足高并发能力不仅仅是分布式解耦、读写分离、限流削峰、缓存、队列,当前还有我们代码编写层面的多线程运用,让单位时间尽可能快的完成业务功能以提升系统吞吐量,吞吐量上来了QPS/TPS自然会提升。所以,今天我们主要对并发基础之线程池简要说明。
何为线程池
线程池英文 Thread Pool,是一种线程处理形式,望文生义就是一个装满线程的池子。当我们需要处理任务时直接从线程池中抓取线程执行,从而减少创建线程开销,避免创建过多线程影响系统开销,也为了尽可能压榨资源提升系统运行效率。
线程池优势
使用线程池的优点我们可以总结为以下几点:
1、重复使用线程,避免频繁创建线程开销,提升系统性能;
2、提供定时调度、单线程、并发数量控制功能,方便实现具体业务场景;
3、灵活的并发线程数量控制,尽可能多的压榨资源提升系统效率,避免过多线程阻塞系统;
4、提供了线程监控功能,可以监控系统运行资源情况
创建线程池方式
直接实例化ThreadPoolExecutor类
直接实例化ThreadPoolExecutor类,传入自定义构造参数
private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(// 线程池核心池的大小1,// 线程池的最大线程数2,// 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间1,// 等待的时间单位TimeUnit.SECONDS,// 用来储存等待执行任务的队列new ArrayBlockingQueue<Runnable>(10),//线程工厂new ThreadPoolExecutor.DiscardOldestPolicy());
JUC Executors 创建线程池
Executors 类有很多创建线程池的构造方法,如:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
其本质上还是调用的ThreadPoolExecutor 线程执行类的构造方法。
线程池挖掘
Executors简单介绍
Java JUC 包下Executors类提供了多种创建线程池的方法:
总的来说我们可以分为如下几种线程池类型:
1、Executors.newCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了需要,可以灵活回收空闲线程,如果没有可回收线程,则新建线程
2、Executors.newFixedThreadPool:创建一个定长的线程池,可以控制线程的最大并发数,超出的线程会在队列中等待
3、Executors.newScheduledThreadPool:创建一个定长的线程池,支持定时、周期性的任务执行
4、Executors.newSingleThreadExecutor:创建一个单线程化的线程池,使用一个唯一的工作线程执行任务,保证所有任务按照指定顺序(先入先出或者优先级)执行
5、Executors.newSingleThreadScheduledExecutor:创建一个单线程化的线程池,支持定时、周期性的任务执行
6、Executors.newWorkStealingPool:创建一个具有并行级别的work-stealing线程池
ThreadPoolExecutor核心类
上文已经讲述了我们创建线程池常见的几种方式,这些方式JUC下Executors都已经提供。那么,这些常用的方法是如何创建线程池的呢?我们先查看选择一个创建方式查看源码:
//Executors.newFixedThreadPool 创建定长线程池方式
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
查看源码可知,创建一个定长线程池的静态方法内部实例化了一个线程池执行类ThreadPoolExecutor,再次进入ThreadPoolExecutor类查看源码:
//ThreadPoolExecutor 线程池执行类的一个有参数构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
// ThreadPoolExecutor 线程池执行类内部构造方法,
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
}
查看源码可知,创建线程池是本质上是实例化了一个ThreadPoolExecutor 线程执行类,且传入了多个构造参数。ThreadPoolExecutor 线程执行类内部此时仅仅是将这些配置参数赋值给这些变量,已备后续线程池执行时候对线程的创建、销毁、调用等操作。
根据线程池的使用场景,我选用excute() 执行方法进行源码解读:
public void execute(Runnable command) {if (command == null)throw new NullPointerException();/** Proceed in 3 steps:** 1. If fewer than corePoolSize threads are running, try to* start a new thread with the given command as its first* task. The call to addWorker atomically checks runState and* workerCount, and so prevents false alarms that would add* threads when it shouldn't, by returning false.** 2. If a task can be successfully queued, then we still need* to double-check whether we should have added a thread* (because existing ones died since last checking) or that* the pool shut down since entry into this method. So we* recheck state and if necessary roll back the enqueuing if* stopped, or start a new thread if there are none.** 3. If we cannot queue task, then we try to add a new* thread. If it fails, we know we are shut down or saturated* and so reject the task.*/int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);
}
如源码所示,可知excute()方法主要做了如下三件事:
1、如果运行线程少于corePoolSize 核心线程会尝试启动一个新线程执行任务,并在addWorker方法中检验runState和workerCount以防止报警。
2、如果任务可以成功排队,也要检查是否应该新建一个线程。因为可能在上次检查后已有线程死亡或者线程池关闭,这个时候就需要回滚排队重新创建一个线程执行任务。
3、如果任务不能排队,我们应该尝试新增一个线程。如果新增线程失败我们应该知道已经关闭或者饱和,此时就会用拒绝策略提示用户
当然还有其他的一些方法如:submit()提交任务、shutdown()关闭线程池、shutdownNow()立即关闭线程池。这些源码也较为简单,可以自行阅读。
ThreadPoolExecutor 类构造参数含义
corePoolSize:核心线程数量
maximumPoolSize: 最大线程数量
keepAliveTime:空闲线程存活时间,当线程数量大于corePoolSize核心线程数,且这些线程处于空闲状态,你们超过这个存活时间的线程将被销毁
TimeUnit:线程时间单位
BlockingQueue:线程阻塞队列,我们可以根据自身情况传入喜欢的阻塞队列
ThreadFactory: 线程创建工厂
RejectedExecutionHandler:线程池拒绝策略,线程池根据传入的配置参数在特定情况下会触发拒绝策略,目前常用的拒绝策略有:
1、直接抛出异常,这也是默认的策略,实现类为AbortPolicy;
2、用调用者所在的线程来执行任务,实现类为CallerRunsPolicy;
3、丢弃队列中最靠前的任务并执行当前任务,实现类为DiscardOldestPolicy;
4、直接丢弃当前任务,实现类为DiscardPolicy。
线程池运行规则
线程池执行任务运行规则如下:
1、如果运行线程数小于 corePoolSize 核心线程数,无论核心线程是否空闲都会新建一个线程执行
2、如果运行线程数大于、等于 corePoolSize 核心线程数,小于 maximumPoolSize 最大线程数,如果BlockingQueue 阻塞队列已满则新建一个线程执行,如果没有满则放入阻塞队列等待空闲线程执行
3、如果运行线程数大于maximumPoolSize,且BlockingQueue 阻塞队列已满则会执行RejectedExecutionHandler 异常策略,默认是直接抛出异常
4、如果运行线程数据大于 corePoolSize 核心线程数量,且存在空闲线程的情况,空闲线程会在 keepAliveTime 存活时间超时被踢掉,直至线程数等于 corePoolSize 核心线程数量
线程设置数量
1、对于CPU密集型任务,需要尽量的压榨CPU,一般建议线程数量为 nCPU + 1
2、对于IO密集型任务,一般建议线程数量为 2nCPU
结语
线程池的灵活运用是多线程开发以满足高并发场景的一大利器,在开发高并发功能业务时候,应当合理使用多线程。特别是应当理解 ThreadPoolExecutor 核心类源码设计,以便于我们创建出适宜的线程池。水能载舟亦能覆舟,良好多线程运用可以提升系统吞吐量,滥用多线程也会导致异常情况的发生。
相关文章:

并发基础之线程池(Thread Pool)
目录前言何为线程池线程池优势创建线程池方式直接实例化ThreadPoolExecutor类JUC Executors 创建线程池线程池挖掘Executors简单介绍ThreadPoolExecutor核心类ThreadPoolExecutor 类构造参数含义线程池运行规则线程设置数量结语前言 相信大家都知道当前的很多系统架构都要求高…...

【C语言进阶】内存函数
天生我材必有用,千金散尽还复来。 ——李白 目录 前言 一.memcpy函数 1.实现memcpy函数 2.模拟实现memcpy函数 二.memmove函数 1.实现memmove函数 2.模拟实现memmove函数 三.memcpy函数和memmove函数的关系 四.memcm…...

Java开发 - ELK初体验
前言 前面我们讲过消息队列,曾提到消息队列也具有保存消息日志的能力,今天要说的EL看也具备这个能力,不过还是要区分一下功能的。消息队列的日志主要指的是Redis的AOF,实际上只是可以利用了消息队列来保存,却并不是消…...

AI_Papers周刊:第六期
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 2023.03.13—2023.03.19 文摘词云 Top Papers Subjects: cs.CL 1.UPRISE: Universal Prompt Retrieval for Improving Zero-Shot Evaluation 标题:UPRISE:改进零样本评估…...
JS运行环境、包管理、打包工具总结
🌳JS运行环境-node.js 运行环境就是代码解析和执行的程序,比如jvm等虚拟机,他们的主要工作就是根据设定的语法规则解析编译代码,然后运行代码。 js的语法规则遵循ES规范。 🍁node.js Node.Js官网 Node.js是一种基于Ch…...

day4网络编程(广播和组播)
1.广播 发送端(类似于客户端) 流程: 创建套接字 填充接收端(服务器)网络信息结构体 bind(非必须绑定) 设置允许广播 向接收端(服务器)发送数据 关闭套接字文件 #include <stdio.h> #in…...
Vue3 自动引入组件及函数、动态生成侧边栏路由
Vue3 自动引入组件及函数、动态生成侧边栏路由 1、安装依赖 npm install -D unplugin-auto-import unplugin-icons unplugin-vue-components插件使用说明 unplugin-auto-import 说明 —— 自动引入函数、组件 unplugin-vue-components 说明 —— 自动注册组件 unplugin-ic…...

人工智能交互系统界面设计
文章目录前言一、项目介绍二、项目准备三、项目实施1.导入相关库文件2.人脸信息验证功能3.语音交互与TCP数据通信4.数据信息可视化四、相关附件前言 在现代信息化时代,图形化用户界面(Graphical User Interface, GUI)已经成为各种软件应用和…...

蓝桥杯嵌入式第一课--创建工程
概述学习本节之前,必须要先安装好 keil5 以及 CubeMX 等软硬件环境,如果你已经安装完成,请告诉自己:考试现在开始!从CubeMX开始CubeMX是创建工程模板的软件,也是我们比赛时第一个要进行操作的软件。一、选择…...

Java面向对象:接口的学习
本文介绍了Java中接口的基本语法, 什么是接口, java中的接口 语法规则, 接口的使用,接口的特性,如何实现多个接口,接口间的继承,以及抽象类和接口的区别 Java接口的学习一.接口的概念二.Java中的接口1.接口语法规则2.接口的使用3.接口的特性4.实现多个接口5.接口间的继承三.抽象…...

西瓜视频登录页面
题目 代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>登录页面</title><style>td{width: 160px;height: 25px;}img{width: 20px;height: 20px;}.number, .password{background: rgba(0,0,0,.05);}.numbe…...
【springboot】常用快捷键:
Ctrl快捷键介绍Ctrl F在当前文件进行文本查找 (必备)Ctrl R在当前文件进行文本替换 (必备)Ctrl Z撤销 (必备)Ctrl Y删除光标所在行 或 删除选中的行 (必备)Ctrl X剪切光标所在行…...
宝塔控制面板常用Linux命令大全
宝塔面板是站长朋友们常见的一款服务器运维面板,可以通过 Web 端轻松管理服务器,提升运维效率。大家在服务器中安装宝塔面板会用到宝塔面板特定的脚本命令。今天这篇文章为大家整理汇总了宝塔面板常用Linux命令,这样方便大家收藏查找。 1、安…...

C语言实现单链表(超多配图,这下不得不学会单链表了)
目录 一:什么是链表? 二:创建源文件和头文件 (1)头文件 (2)源文件 三:实参和形参 四:一步步实现单向链表 (1)建立一个头指针并置空 (2)打印链表,便于…...

SQL编写优化技巧
一、底层原理 sql慢是因为没有走索引,因此需要添加索引然它走索引联合索引需要匹配最左匹配原则(索引回表)如果查询列超出索引的key, 会导致回表,回表数量多,则会走全表扫描 索引是分聚集索引、非聚集索引…...

【基础算法】单链表的OJ练习(6) # 复制带随机指针的链表 #
文章目录🍇前言🍎复制带随机指针的链表🍑写在最后🍇前言 本章的链表OJ练习,是最后的也是最难的。对于本题,我们不仅要学会解题的思路,还要能够通过这个思路正确的写出代码,也就是思路…...
Activity生命周期完成EvenetLog回调
Activity 生命周期 系统EvenetLog回调 EventLog路径: Android13/frameworks/base/core/java/android/app/EventLogTags.logtags wm_on_create_called wm_on_restart_called wm_on_start_called wm_on_resume_called wm_on_top_resumed_gained_called wm_on_top_resumed_lost_c…...

西安石油大学C语言期末真题实战
很简单的一道程序阅读题,pa’默认为a【0】,接下来会进行3次循环 0 1 2 输出结果即可 前3题就是一些基础定义,在此不多赘述 要注意不同的数据类型的字节数不同 a<<2 b>>1(b>>1;就是说b自身右位移一位(…...
【Shell】Shell变量
Shell变量系统预定义变量自定义变量基本语法定义变量撤销变量命名规则使用变量只读变量删除变量变量类型系统预定义变量 $HOME、$PWD、$SHELL、$SUSER等 实例 yysubuntu:~$ echo $HOME #查看系统变量的值 /home/yys yysubuntu:~$ set #显示当前shell中所有变量自定义变量…...

你是真的“C”——结构体中鲜有人知的“秘密”
你是真的“C”——结构体中的精髓剖析【内存对齐】 【位段】 😎前言🙌结构体内存对齐:😊结构体内存对齐存在的意思是什么?😘内存对齐例子详细剖析:😘结构体中的位段:&…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...