【Java并发】聊聊线程池原理以及实际应用
线程其实对于操作系统来说是宝贵的资源,java层面的线程其实本质还是依赖于操作系统内核的线程进行处理任务,如果频繁的创建、使用、销毁线程,那么势必会非常浪费资源以及性能不高,所以池化技术(数据库连接池、线程池)在性能优化的时候是重中之重。
我们来猜想以下线程池的功能,因为如果是一个线程一个线程执行任务,那么我们需要进行对线程的管理、以及对于任务的分配,在执行过程中,本质还是利用多个线程通过从任务队列中获取任务,进行执行。通过这种方式,当任务来临时可以直接使用线程,达到复用。
demo
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(15),new ThreadFactory() {private final AtomicInteger atomicInteger = new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "pool-" + atomicInteger.getAndIncrement());}}, new ThreadPoolExecutor.DiscardPolicy());//执行pool.execute(new Runnable() {@Overridepublic void run() {System.out.println("qxlxi");}});//关闭pool.shutdown();boolean terminated = false;while (!terminated) {pool.awaitTermination(100,TimeUnit.SECONDS);}System.out.println("pool is shutdowm.");
线程池的创建
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
corePoolSize :线程池中的常驻核心线程数 < core: >core : 缓冲队列,超过缓冲队列,就直接新建。核心线程不回销毁,而非核心线程超过一定时间没有使用,就会销毁。
maxmumPoolSize : 整个线程池的核心线程和非核心线程数, maxmumPoolSize - corePoolSize 就是非核心线程数。
keepAliveTime & unit : 非核心线程数销毁的时间,可以自定义
workQueue :当有新的任务请求线程时,超过核心线程数,那么就会将任务先存储到任务队列中,等待线程处理。是一个阻塞队列。
有Array、Linked、Priority、Synchron等。
handler : 当没有空闲线程进行处理任务的时候,超过来最大线程数,那么就需要执行线程池的拒绝策略。可以通过hanlder进行设置。只针对有届阻塞队列 可以看到通过一个抽象的接口,然后实现不同的策略来进行执行拒绝策略,当然我们也可以实现自己的策略拒绝类。
public interface RejectedExecutionHandler {void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
DiscardPolicy 什么也不做。
public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}
AbortPolicy 直接返回异常。不执行
public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() +" rejected from " +e.toString());}}
CallerRunsPolicy 策略是 任务提交者来执行这个任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {public CallerRunsPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}}
DiscardOldestPolicy 策略是:判断线程是否关闭状态,没有关闭的化,直接删除workQueue中的一个任务,然后将其加入其中。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}}
threadFactory
线程创建ThreadPoolExecutor对象时,传入ThreadFactory工厂类对象,那么线程池中的对象均会通过工厂类的new Thread()方法来实现。可以通过定义new Thread()对象来创建,添加一些信息。当然,我们还可以通过 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor等方式。

线程池的执行
线程池执行任务的时候,只需要将执行的任务封装成Runnable对象,然后将Runnable对象传递给execute()函数,线程池在创建的时候,并不会提前创建,而是当有任务的时候才会创建。
1.核心线程是否已满,没有满 直接创建
2.核心线程已满,则检查等待队列是否已满,未满,将任务放入队列中
3.等待队列已满,检查非核心线程,非核心线程未满,常见非核心线程
4.核心线程、等待队列、非核心线程都满了,执行对应的拒绝策略

整体的流程,其实是创建核心线程之后,就会从wrokQueue中获取任务通过take()函数进行执行,如果没有任务的化就会阻塞等待,非核心线程创建之后,会调用workQueue()的poll(),不同从workQueue()获取任务,poll()函数是阻塞函数,根take()函数不同的时,poll()函数可以设置阻塞的超时时间,poll()的超时时间超过非核心线程的等待时间,那么就会超时返回,执行线程销毁。
关闭
public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();}
public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}
线程池关闭有两个方法,一种是shutdown() 以及shutdownNow()。前者是通过优雅的方式,会执行完正在执行以及等待队列中的任务,后者则是通过直接将处理中,以及清空等待队列,并向所有线程发送中断请求。shutdownNow的返回值是等得队列中未被执行的任务。需要注意的是返回时,有可能线程池内还有线程在执行任务,需要等所有线程都执行完毕之后,调用awaitTermination函数阻塞等待。
配置
在实际的应用开发中,我们如何进行合理配置线程池的大小呢,一般通俗来说的化,IO密集型和计算密集型,计算密集型设置未CPU核心相当就可以,IO密集型因为大部分时间都在IO阻塞上,所以将线程池适当开大点。除此之外就是IO+计算相结合的方式。
具体的方式其实就需要统计花在IO和计算上的占比,pool_size * 核数 = (cpu_time + io_time) / cpu_time。
比如cpu_time 占用 1/3 io占用 2/3 那么就是3.
当然在实际的层面来说,还需要考虑别的地方有没有瓶颈,比如数据库连接池,文件句柄等。也就是木桶效应。根据最短的进行合理评估,在实际中,就遇到DB 连接池配置失效,当时吞吐量上不去,最后发现后才解决。
小结
参数说明:
corePoolSize:指定了线程池中的线程数量。
maximumPoolSize:指定了线程池中的最大线程数量。
keepAliveTime:当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间内会被销毁。CachedThreadPool是60秒。
unit: keepAliveTime的单位。
workQueue:
blockQueue的配置。新加入任务的时候,当线程池中的可用线程小于第一个参数core线程数量,那么直接new一个线程或者用空闲的可用线程来执行任务。不用进行排队。当线程池中core线程数量都处于执行中,那么就把任务加入到blockqueue中进行排队等待。当排队队列满了,那么新new一个线程,执行最新加入到queue中的任务。如果线程池中的线程数量超过了最大线程数量,那么这个时候将拒绝新加入的任务。如果最大线程数都满了,队列中也满了,这个时候,还有新任务请求进来,那么会报错,默认是抛出RejectedException。
blockqueue有三种策略,
第一种是配置一个SynchronousQueue,这种queue其实不是真正的queue,他根本就不会进行排队,如果core线程数量满了,那么新来一个任务,会直接new一个线程,而不是进入排队!直到池中的线程数量超过最大线程数,开始拒绝新加入的任务,JDK的CacheThreadLocal就是使用的这个工作队列,配置的最大线程数是Integer.MAXVALUE。
第二种是配置一个LinkedBlockingQueue,这种queue本身是没有大小的,也就是说,这种queue永远也满不了,可以无限排,这个时候最大线程数就没有意义了,因为queue永远不满,所以,这种配置就相当于是一个固定的大小为core线程数的线程池,JDK的FixedThreadPool就是采用的这个
第三种策略是用ArrayBlockingQueue,我们可以给这个queue指定大小。比如200啊,300啊,那么,只要queue大小满了,就会产生新的线程来处理queue的头部任务。如果池中超过了最大线程数,那么会拒绝任务的加入。
threadFactory:线程工厂,用于创建线程,一般用默认的即可。如果默认的不能满足我们的要求,我们可以使用自定义的线程工厂。
RejectedExceptionHandler:拒绝策略,当BlockQueue都满了无法接收新的任务了,就会触发RejectedExceptionHandler的方法了,这是一个策略模式的很好的例子。JDK提供了几种现成的拒绝策略,默认的拒绝策略是AbortPolicy,抛出RejectedException,这是一个运行时的异常,但是线程池执行器依旧可以继续工作,再次提交新的任务的时候,可能又会抛出RejectedException。CallerRunsPolicy,这个策略是在调用者的线程中运行被抛弃的任务,相当于在线程池submit任务的时候,在调用者线程中执行runnable,显然,这个策略很糟糕;DiscardoldestPolicy,这个策略是将队列中最老的挤出去抛弃掉,然后再次提交该任务;DiscardPolicy,直接丢弃无法处理的任务,不做任何处理。一般来说,默认的拒绝策略是最好的,但是如果我们还想要自定义的拒绝策略,我们可以自己实现一个RejectedExceptionHandler策略。
相关文章:
【Java并发】聊聊线程池原理以及实际应用
线程其实对于操作系统来说是宝贵的资源,java层面的线程其实本质还是依赖于操作系统内核的线程进行处理任务,如果频繁的创建、使用、销毁线程,那么势必会非常浪费资源以及性能不高,所以池化技术(数据库连接池、线程池&a…...
自然语言处理常用方法和评价指标
常用方法 文本分类:如情感分析、主题标签分类。使用方法如朴素贝叶斯、支持向量机、神经网络等。信息提取:从文本中提取结构化信息,如命名实体识别(NER)、关系提取。语义分析:理解文本的含义,包…...
FFmpeg常用命令行讲解及实战一
文章目录 前言一、学习资料参考二、FFmpeg 选项1、主要选项①、主要命令选项②、举例 2、视频选项①、主要命令选项②、举例1)提取固定帧2)禁止输出视频3)指定视频的纵横比 3、音频选项①、主要命令选项②、举例 4、字幕选项①、主要命令选项…...
Java的ArrayList中关于删除的常用操作及方法
目录 remove(int index)方法 remove(Object o)方法 removeAll(Collection c)方法 removeIf(Predicate filter)方法 removeRange(int fromIndex, int toIndex)方法 remove(int index)方法 remove(int index)是ArrayList类中用于删除指定位置元素的方法。它接收一个整…...
低成本打造便携式无线网络攻防学习环境
1.摘要 一直以来, 无线网络安全问题与大众的个人隐私息息相关, 例如: 为了节省流量, 连接到一个看似安全的免费WiFi, 在使用过程中泄露自己的各类密码信息甚至银行卡账号密码信息。随着家用智能电器的普及, 家中的各类智能设备连入家里的无线网络, 却突然失灵, 甚至无法正常连…...
Qt|QLabel显示刷新图像数据
参考:QImage、QClipboard(https://zhuanlan.zhihu.com/p/649611141) 获取图像数据并转换为QImage unsigned char *data 图像数据; QImage show_image_ QImage(data, imgInfo.width, imgInfo.height, imgInfo.width, QImage::Format_Grays…...
Java类加载那些事
Java源文件(.java文件)被编译器编译后变为字节码形式的类文件(.class文件),Java类加载的过程就是JVM加载.class的二进制文件并且放到内存中,将数据放到方法区,并且在堆区构造一个java.lang.clas…...
QSplitter分裂器
QSplitter QSplitter 是 Qt 框架提供的一个小部件(widget),用于在用户界面中创建可拖动的分割窗口,允许用户调整子部件的大小和布局。它可以将父部件分割为多个可调整大小的子部件,使用户能够自定义界面的布局和大小。…...
pgsql 时区查看和修改
建议使用UTC时区,或者和linux、后端程序的时区保持一致,否则容易出现时间的差别。 pgsql的时间字段有一个带时区的timestamp with time zone,如果业务涉及多个时区,建议使用这个字段。 相关链接参考: linux时区设置和…...
el-table 表格表头、单元格、滚动条样式修改
.2023.11.21今天我学习了如何对el-table表格样式进行修改,如图: 运用的两个样式主要是 1.header-cell-class-name(设置表头) 2.class-name(设置行单元格) 代码如下: <el-table :data&quo…...
dockerDesktop使用方法
安装软件 装在C盘会容易满,可以装在D盘, "path\to\Docker Desktop Installer.exe" install -accept-license --installation-dirD:\Docker\Docker --wsl-default-data-rootD:\Docker\data并且在软件的设置的Docker Engine里添加阿里镜像源…...
[Ubuntu]RT810xE--网线已拔出--问题解决
0 环境 ubuntu 22.04.3 LTSDell Inspiron 15 5547windows/ubuntu 双系统 1 问题说明 Dell 笔记本安装的 Ubutun 系统,有线网络无法使用,一直显示 “网线已拔出”。 网上一查,才了解到主要原因:网卡驱动安装错误。系统默认安装…...
美国DDoS服务器:如何保护你的网站免遭攻击?
在当今数字化时代,互联网已经成为人们生活中不可或缺的一部分。随着互联网的普及和发展,网络安全问题也日益严重。其中,DDoS攻击是目前最常见和具有破坏性的网络攻击之一。那么,如何保护你的网站免遭DDoS攻击呢?下面将介绍…...
R语言数据缩放-1到1
目录 普通scale -1到1限定范围scale 普通scale R语言实战:scale()函数 - 知乎 (zhihu.com) scale(x, center TRUE, scale TRUE) 过程: 对每个变量(列)计算平均值(mean)和标准…...
C语言第二十五弹--打印菱形
C语言打印菱形 思路:想要打印一个菱形,可以分为上下两部分,通过观察可以发现上半部分星号的规律是 1 3 5 7故理解为 2对应行数 1 ,空格是4 3 2 1故理解为 行数-对应行数-1。 上半部分代码如下 for (int i 0;i < line;i){//上…...
PyTorch微调终极指南1:预训练模型调整
如今,在训练深度学习模型时,通过根据自己的数据微调预训练模型来进行迁移学习(transfer learning)已成为首选方法。 通过微调这些模型,我们可以利用他们的专业知识并使它们适应我们的特定任务,从而节省宝贵…...
Uptime Kuma 企业微信群机器人告警
curl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa \-H Content-Type: application/json \-d {"msgtype": "text","text": {"content": "hello world"}}企业微信群机器人ke…...
【网络安全】-网络安全的分类详解
文章目录 介绍1. 网络层安全(Network Layer Security)理论实操使用VPN保护隐私 2. 应用层安全(Application Layer Security)理论实操使用密码管理器 3. 端点安全(Endpoint Security)理论实操定期更新防病毒…...
php利用ZipArchive类实现文件压缩与解压
github项目 1、Linux 安装zlib库 cd /usr/local/src wget https://zlib.net/current/zlib.tar.gz tar -zxvf zlib.tar.gz cd zlib-1.3 ./configure make && make install 2、zlib的使用 $all_name all.zip;// 创建ZipArchive对象$zip_all new ZipArchive();if ($z…...
Java面试附答案:掌握关键技能,突破面试难题!
问题:什么是大O表示法?它在Java中的应用是什么? 回答: 大O表示法是一种用来衡量算法复杂度的方法,它描述了算法的时间复杂度和空间复杂度的增长速度。它使用符号O(n)来表示算法的渐进时间复杂度,其中n表示…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
