线程池【开发实践】

news2024/9/21 22:52:52

文章目录

  • 一、为什么要用线程池
    • 1.1 单线程的问题
    • 1.2 手动创建多线程的问题
    • 1.3 线程池的作用(优点)
    • 1.4 线程池的使用场景
  • 二、线程池的基础知识
    • 2.1 线程池的核心组件
    • 2.2 JUC中的线程池架构
    • 2.3 线程池的配置参数
    • 2.4 线程池常见的拒绝策略(可自定义)
    • 2.5 线程池的生命周期
    • 2.6 线程池的任务调度规则
    • 2.7 线程池执行器的钩子方法
  • 三、使用基础
    • 3.1 常见的工作队列
    • 3.2 Executors提供的线程池
    • 3.3 使用Executors创建线程的问题
  • 四、开发实践
    • 4.1 线程数量配置
    • 4.2 提交任务:以submit(callable)为例
    • 4.3 关闭线程池
  • 参考

一、为什么要用线程池

1.1 单线程的问题

单线程无法利用CPU的多核资源,且存在阻塞问题。使用单个线程来处理一组任务,若当前任务阻塞了线程,该线程将失去CPU的使用权,而不是去执行其他可以执行的任务。

1.2 手动创建多线程的问题

需要开发人员去管理线程的生命周期,麻烦,且可能出错。
还存在并发数不可控的问题,如每次到达一个任务时都新建一个线程来处理,当大量任务到达时,会创建大量的线程,导致线程间的竞争加剧,且可能导致系统资源耗尽。

1.3 线程池的作用(优点)

  • 节省线程创建和销毁的开销:线程池通过复用一组线程,可以节省频繁创建和销毁线程的开销。
  • 控制并发数:线程池可以控制最大并发数,避免线程过多导致系统资源耗尽。
  • 提高响应速度:当任务到达时,可以直接使用线程池中已经创建好的线程,节省了新建线程的时间。
  • 管理线程:线程池负责内部线程的生命周期管理,包括创建、销毁和调度等,无需人工干预。

1.4 线程池的使用场景

  • 处理并发请求:每个请求都要一个线程来处理,使用线程池来复用一组线程,能节省创建和销毁线程的开销。
  • 执行异步任务:避免耗时操作(如邮件发送、文件上传)阻塞主线程,可以使用线程池来异步执行这些任务。
  • 处理IO密集型任务:线程在进行IO操作时会阻塞,使用线程池来调度这些任务,可以在任务阻塞时调度其他任务继续执行,避免CPU空闲。
  • 处理计算密集型任务:使用线程池来执行计算密集型任务,可以充分利用CPU的多核资源,也能控制并发数量避免系统资源耗尽。
  • 后台服务:利用线程池来执行后台服务,与前台线程的执行独立,互不影响。
  • 执行定时任务与周期性任务:可以使用ScheduledThreadPool在后台执行这些任务,与前台线程的执行独立。

二、线程池的基础知识

2.1 线程池的核心组件

  • 线程池管理器:负责线程池的创建、销毁、管理和配置。
  • 工作线程:工作线程是线程池中的实际工作者,负责执行提交给线程池的任务。
  • 任务接口:每个提交到线程池的任务都需要实现一个任务接口。Runnable用于定义无需返回值的任务,而Callable则用于定义可以有返回值的任务,并且可以抛出异常。
  • 工作队列(任务队列):用于暂存等待被执行的任务,有多种实现方式,未必是先进先出。
  • 线程工厂:线程工厂的作用是定义一个规范来创建线程,允许在创建线程时进行定制化设置,例如设置线程的名称、优先级、是否为守护线程(daemon thread)等属性。
  • 拒绝策略:见后。
    (后三个核心组件是线程池的后三个配置参数)

2.2 JUC中的线程池架构

Executor
是线程池管理器的顶层接口,只定义了一个方法void execute(Runnable command),用来提交Runnable任务。

ExecutorService
该接口扩展了Executor,增加了更多管理线程池的方法,如提交Callable任务、提交批量任务、线程池的生命周期管理(shutdown()shutdownNow())、获取线程池状态等。

AbstractExecutorService实现了该接口中的部分方法,为创建自定义的线程池服务实现提供了基础框架。

ThreadPoolExecutor继承自AbstractExecutorService,是JUC中线程池管理器的核心实现类。

Executors是一个静态工厂类,提供了多种线程池管理器。

ScheduledExecutorService
该接口扩展了ExecutorService,增加了定时执行和周期性执行任务的功能。ScheduledThreadPoolExecutor实现该接口,专为计划任务而设计。
在这里插入图片描述

2.3 线程池的配置参数

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:空闲线程存活时间。当线程池中的工作线程数量超过核心线程数时,额外的工作线程在空闲这个时间段后会被终止,直到工作线程数量降到核心线程数。但如果设置了allowCoreThreadTimeOuttrue,那么核心线程也可以被终止。
  • unit:空闲线程存活时间的单位,可以用枚举类TimeUnit设置。
  • workQueue:工作队列。
  • threadFactory:线程工厂。
  • handler:拒绝策略。当阻塞队列已满且工作线程数大于等于最大线程数时,无法接受新的任务,会按照拒绝策略进行处理。

2.4 线程池常见的拒绝策略(可自定义)

  • AbortPolicy:拒绝策略。是默认策略,会将新任务拒绝,并且抛出RejectedExecutionException异常。
  • DiscardPolicy:抛弃策略。会将新任务丢掉,但不会跑出异常。
  • DiscardOldestPolicy:抛弃最老任务策略。不是丢弃新任务,而是先丢弃最先进入队列的任务,然后将新任务入队。
  • CallerRunsPolicy:调用者执行策略。让提交任务的线程去执行新任务,而不是使用线程池中的线程去执行。
    在这里插入图片描述

2.5 线程池的生命周期

  • New:新建状态。当线程池创建后,但尚未开始执行任务时,它处于新建状态。此时,线程池中的线程尚未启动。
  • Running:运行状态。新建的线程池开始接受并处理任务后就会进入运行状态,这是线程池的正常工作状态,可以接收新任务和处理工作队列中的任务。
  • Shutdown:关闭状态。调用shutdown()方法后,线程池会进入关闭状态。此时,线程池不再接受新任务,但不会立刻终止,它会继续处理队列中已有的任务直到所有任务完成。
  • Stop:停止状态。调用shutdownNow()方法后,线程池会进入停止状态,此时会中断正在执行任务的线程,清空任务队列,并返回未开始执行的任务列表。
  • Tidying:整理状态。完成关闭状态或停止状态的工作后,线程池会进入整理状态。此阶段,线程池中的线程数量降为0,即将调用terminated()钩子方法。
  • Terminated:终止状态。在整理状态后,线程池调用terminated()方法并进入终止状态。此时线程池生命周期结束,会释放所有资源。
    在这里插入图片描述

2.6 线程池的任务调度规则

当向线程池提交一个任务时:

  1. 若线程池中的工作线程数量小于核心线程数,执行器总是优先创建一个新的工作线程来执行任务,而不是使用一个空闲的工作线程(目的是快速让线程池中有足够的活跃线程)。若工作线程数量大于等于核心线程数,则根据工作队列的情况进行相应的处理。
  2. 若工作队列未满,则将任务入队。若工作队列已满,则根据工作线程数量与最大线程数的关系进行相应的处理。
  3. 若工作线程数大于等于最大线程数,则执行拒绝策略。否则,会新建一个非核心线程来立即执行新的任务。
    在这里插入图片描述

2.7 线程池执行器的钩子方法

beforeExecute(Thread t, Runnable r): 任务执行之前的钩子方法。
afterExecute(Runnable r, Throwable t): 任务执行之后的钩子方法。
terminated(): 线程池终止时的钩子方法。

三、使用基础

3.1 常见的工作队列

工作队列基于阻塞队列实现,我们可以实现BlockingQueue来自定义阻塞队列,也可以使用JUC中提供的阻塞队列:

  • ArrayBlockingQueue:基于数组的有界阻塞队列。队列大小是固定的,在创建时必须指定。由于是基于数组,所以访问速度快,但可能有扩容受限的问题。
  • LinkedBlockingQueue:基于链表的阻塞队列。如果不指定容量,则默认为Integer.MAX_VALUE。相比ArrayBlockingQueue,插入和删除操作可能稍微慢一点,但由于链表的动态性,它可以更灵活地调整大小。
  • PriorityBlockingQueue:基于最小堆的无界的优先级队列,元素按照自然排序或提供的比较器进行排序。任务按照优先级顺序被处理,而非先进先出。
  • DelayQueue:基于PriorityBlockingQueue的无界阻塞队列,其中元素只有在延迟期满后才能被获取,否则将阻塞等待,常用于实现定时任务和延迟操作。
  • SynchronousQueue:一个特殊的队列,它没有内部容量,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。适用于直接的生产者-消费者传递,非常适合传递性操作。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列,支持公平和非公平模式。它还提供了一个tryTransfer()方法,允许生产者直接将元素转移给等待中的消费者,如果没有消费者等待,则可以选择是否阻塞(生产者可阻塞)。
  • LinkedBlockingDeque:双向链表实现的双向阻塞队列,既可以用作栈,也可以用作队列。它支持有限或无界的队列,并提供了putFirst()putLast()等方法来控制元素的插入位置。

3.2 Executors提供的线程池

在这里插入图片描述

3.3 使用Executors创建线程的问题

  • 定长线程池/单线程话线程池:阻塞队列无界,可能导致JVM出现OOM(Out Of Memory)异常。
  • 定时线程池/可缓存线程池:最大线程数量不设限上,如果任务提交较多,就会造成大量的线程被启动,可能造成OOM异常,也可能导致CPU线程资源耗尽。

四、开发实践

4.1 线程数量配置

IO密集型
核心线程数设置得比CPU核心数稍大,因为当线程在等待I/O时,CPU可以调度其他线程执行。最大线程数可以设置得更高,甚至远大于CPU核心数,具体数值取决于系统的I/O能力及预期的并发水平。一般推荐设置为2 * CPU核心数或更高,但需注意不要设置得过高以免过度消耗系统资源。
计算密集型
核心线程数和最大线程数通常设置为CPU核心数,因为在这种情况下,更多的线程并不能带来性能提升,反而会因为上下文切换带来额外开销。

4.2 提交任务:以submit(callable)为例

	 // 创建线程池
	ExecutorService executor = Executors.newSingleThreadExecutor();

	// 创建Callable任务对象
	Callable<Result> task = () -> {
		// ...
	};

	// 提交任务并获取Future对象
    Future<Result> future = executor.submit(task); // 提交任务并获取Future

    // 获取结果
    try {
    	// get()方法会阻塞,如果任务执行过程中发生了异常,那么此处会抛出该异常
        Result res = future.get();
    } catch (InterruptedException e) {
    	// 处理中断异常...
    } catch (ExecutionException e) {
        // 处理执行异常...
    }
	
	// 关闭ExecutorService
    executor.shutdown();

4.3 关闭线程池

调用shutdown()shutdownNow()后,当前线程不会等待线程池的关闭,若当前线程结束了,可能导致内存泄露和资源浪费。

若线程池中还有活动的线程,就算主线程结束了,JVM也会因为存在活跃的非守护线程而无法退出,导致应用程序无法正常退出。

awaitTermination()方法用来阻塞当前线程,直到线程池中的所有任务完成执行或者超时,或者当前线程被中断。可以用于等待线程池中的线程全部执行完后再退出当前线程,线程池中的所有线程在超时前执行完毕,会返回true并恢复当前线程的执行。

ExecutorService executorService = Executors.newFixedThreadPool(10);

// ... 提交任务到线程池

// 关闭线程池
executorService.shutdown();
try {
    // 等待一定时间,直到超时或者线程池中的线程全部执行结束
    if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
        // 如果超时,则尝试强制关闭
        executorService.shutdownNow();
        // 等待一定时间,直到超时或者线程池中的线程全部执行结束
        if (!executorService.awaitTermination(60, TimeUnit.SECONDS))
            System.err.println("线程池未在规定时间内停止!");
    }
} catch (InterruptedException ie) {
    // 强制终止线程池
    executorService.shutdownNow();
    // 保持当前线程的中断状态
    Thread.currentThread().interrupt();
}

参考

Java线程池(超详细)
Java 多线程:彻底搞懂线程池

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1911345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

el-image放大图片功能

1.需求&#xff1a;点击图片后放大图片 html代码&#xff1a; <el-imagestyle"width: 100px; height: 100px":src"baseUrl item.id":zoom-rate"1.2":max-scale"7":min-scale"0.2":preview-src-list"srcList"…

淘宝商品历史价格查询(免费)

当前资料来源于网络&#xff0c;禁止用于商用&#xff0c;仅限于学习。 淘宝联盟里面就可以看到历史价格 并且没有加密 淘宝商品历史价格查询可以通过以下步骤进行&#xff1a; 先下载后&#xff0c;登录app注册账户 打开淘宝网站或淘宝手机App。在搜索框中输入你想要查询的商…

Iptables与Firewalld防火墙

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、防火墙管理工具 二、Iptables 三、Firewalld ​四、服务的访问控制列表 五、Cockpit驾驶舱管理工具 致谢 一、防火墙管理工具 防火墙…

js ES6 part1

听了介绍感觉就是把js在oop的使用 作用域 作用域&#xff08;scope&#xff09;规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问&#xff0c; 作用域分为&#xff1a; 局部作用域、 全局作用域 1. 函数作用域&#xff1a; 在函数内部声明的…

【UE5】仅修改结构体的若干个数据

蓝图中的结构体变量 | 虚幻引擎4.27文档 (unrealengine.com) 连线连到傻&#xff0c;因为如果某个变量set空值也一起过去了。一查发现有这个节点。

Windows10系统下mysql5.6的安装步骤

1.下载mysql 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 在这里我们下载zip的包 2.解压mysql包到指定目录 3. 添加my.ini文件 # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.6/en/server-configurat…

最新深度技术Win7精简版系统:免费下载!

在Win7电脑操作中&#xff0c;用户想要给电脑安装上深度技术Win7精简版系统&#xff0c;但不知道去哪里才能找到该系统版本&#xff1f;接下来系统之家小编给大家带来了深度技术Win7系统精简版本的下载地址&#xff0c;方便大家点击下载安装。系统安装步骤已简化&#xff0c;新…

Java客户端调用SOAP方式的WebService服务实现方式分析

简介 在多系统交互中&#xff0c;有时候需要以Java作为客户端来调用SOAP方式的WebService服务&#xff0c;本文通过分析不同的调用方式&#xff0c;以Demo的形式&#xff0c;帮助读者在生产实践中选择合适的调用方式。 本文JDK环境为JDK17。 结论 推荐使用Axis2或者Jaxws&#…

20240709每日后端--------最优解决Invalid bound statement (not found)

目标 最优解决Invalid bound statement (not found) 步骤 1、打包 2、查看target下是否成双成对出现 3、核对无误后&#xff0c;即可解决问题。

重塑智慧生活想象 Yeelight易来举行2024年战略及新品发布会圆满成功

7月9日&#xff0c;智能照明品牌Yeelight易来在广州举行“光为境和无界”——2024年Yeelight易来战略&新品发布会&#xff0c;此次发布会不仅展示了易来在新的一年中取得的显著业绩增长&#xff0c;还发布了多款引领行业潮流的智能新品。同时&#xff0c;发布会还邀请了权威…

Docker搭建kafka+zookeeper以及Springboot集成kafka快速入门

参考文章 【Docker安装部署KafkaZookeeper详细教程】_linux arm docker安装kafka-CSDN博客 Docker搭建kafkazookeeper 打开我们的docker的镜像源配置 vim /etc/docker/daemon.json 配置 { "registry-mirrors": ["https://widlhm9p.mirror.aliyuncs.com"…

mysql查询语句执行流程

流程图 连接器&#xff1a;建立连接&#xff0c;管理连接、校验用户身份&#xff1b;查询缓存&#xff1a;查询语句如果命中查询缓存则直接返回&#xff0c;否则继续往下执行。MySQL 8.0 已删除该模块&#xff1b;解析 SQL&#xff0c;通过解析器对 SQL 查询语句进行词法分析、…

构造二进制字符串

目录 LeetCode3221 生成不含相邻零的二进制字符串 #include <iostream> #include <vector> using namespace std;void dfs(string s,int n,vector<string>& res){if(s.size()n){res.push_back(s);return;}dfs(s"0",n,res);dfs(s"1"…

Zabbix Sia Zabbix 逻辑漏洞(CVE-2022-23134)

前言 CVE-2022-23134是一个中等严重度的漏洞&#xff0c;影响Zabbix Web前端。这个漏洞允许未经身份验证的用户访问setup.php文件的某些步骤&#xff0c;这些步骤通常只对超级管理员开放。利用这个漏洞&#xff0c;攻击者可以通过跳过某些步骤来重新配置Zabbix前端&#xff0c…

代码随想录算法训练营第四十八天| 115.不同的子序列、583. 两个字符串的删除操作、 72. 编辑距离

115.不同的子序列 题目链接&#xff1a;115.不同的子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会 思路&#xff1a; dp[i][j] 表示在 s 的前 j 个字符中&#xff0c;t 的前 i 个字符作为子序列出现的次数。 匹配的情况&#xff1a; 1.当 s[j-1] 与 t[i-1] 匹配…

软考高级里《系统架构设计师》容易考吗?

我还是22年通过的架构考试。系统架构设计师属于软考高级科目&#xff0c;难度比初级和中级都要大&#xff0c;往年的通过率也比较低&#xff0c;一般在10-20%左右。从总体来说&#xff0c;这门科目确实是不好过的&#xff0c;大家如果想要备考系统架构设计师的话&#xff0c;还…

营销宝典:4P理论全方位解读,营销人的必修课

这么说吧&#xff0c;4P是营销的一切&#xff0c;也是一切的营销&#xff01; 4P理论产生于20世纪60年代的美国&#xff0c;随着营销组合理论的提出而出现的。 1953年&#xff0c;尼尔博登&#xff08;NeilBorden&#xff09;在美国市场营销学会的就职演说中创造了“市场营销…

【Python】已解决:SyntaxError: invalid character in identifier

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;SyntaxError: invalid character in identifier 一、分析问题背景 在Python编程中&#xff0c;SyntaxError: invalid character in identifier是一个常见的编译…

java Web 优秀本科毕业论文系统用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 优秀本科毕业论文系统是一套完善的web设计系统&#xff0c;对理解JSP java serlvet 编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&a…

苍穹外卖--导入分类模块功能代码

把各层代码拷贝到所需文件夹下&#xff0c; 进行编译 在运行 提交和推送仓库