java自定义线程池详解

目录

  • 线程池
    • 使用线程池的目的
    • 线程池工作原理
    • 线程池常用方法
    • 自定义线程池
      • 等待队列
      • 拒绝策略
      • 线程工厂

线程池

使用线程池的目的

  • 资源复用,降低开销。重复利用已创建的线程,避免线程频繁地创建和销毁带来的性能开销。
  • 方便线程的可管理性。线程是稀缺资源,使用线程池可以进行统一的分配,调优和监控。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

线程池工作原理

工作原理(借用guide哥的图):

图解线程池实现原理

注意:

  • 刚开始创建线程池的时候不会立马创建好核心线程,而是有任务来了之后再创建核心线程
  • 线程池小于corePoolSize时,就算有线程空闲,也会一定会创建新的线程
  • 当等待队列也满了的时候,又来一个任务,会新创建一个非核心线程,且用于执行刚刚到来的任务,而不是队头的任务
  • 如果设置了allowCoreThreadTimeOut(true),那么核心线程空闲时间达到keepAliveTime也会关闭,但默认不会

线程池常用方法

//使用工具类创建线程池
ExecutorService executor1 = Executors.newFixedThreadPool(3);//定义任务
Runnable task = ()->{System.out.println("线程"+Thread.currentThread().getName()+"正在执行");
};executor.excute(XXX); //执行任务,没有返回值
executor.submit(XXX);//执行任务,返回一个Future,用于获取线程的执行情况executor.shutdown();//正常关闭,停止提交新任务,而已提交的任务可以正常执行,非阻塞
executor.shutdownNow();//立即强制关闭,停止提交新任务,正在运行的任务停止,等待队列也销毁,非阻塞
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); // 阻塞等待所有任务完成并且shutdown,如果超过到达时间,则返回false

当等待队列是无界队列时,非核心线程会创建吗?

不会,因为非核心线程只有在队列满的时候才会创建,使用了无界队列就相当于默认只有核心线程生效

自定义线程池

总结四种线内置程池都相对比较极端,所以一般都建议使用自定义线程池,保证一定的核心线程数(保证活跃)和一定的非核心线程数(提供伸缩)。

工作流程概述:刚开始没有线程,添加任务后创建核心线程,核心线程满了就加到等待队列,等待队列也满了就创建临时线程去执行,临时线程也满了就执行拒绝策略。

ThreadPoolExector线程池完整参数如下:

    public ThreadPoolExecutor(int corePoolSize,//核心线程数int maximumPoolSize,//最大线程数long keepAliveTime,//非核心线程(临时线程)空闲存活时间TimeUnit unit,//存活时间单位BlockingQueue<Runnable> workQueue,//等待队列ThreadFactory threadFactory,//线程工厂RejectedExecutionHandler handler//拒绝策略) 

各个参数详解:

  • corePoolSize:核心线程数,线程池中保持活跃的线程数量。即使线程空闲,核心线程也不会被回收。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。超过此数量的请求将被放入等待队列
  • keepAliveTime:非核心线程的空闲时间,超过此时间后,非核心线程会被回收。
  • unitkeepAliveTime 的时间单位,例如秒、毫秒等。
  • workQueue:等待队列,用于存放被提交但尚未被执行的任务。可以是不同类型的队列,如 LinkedBlockingQueueArrayBlockingQueue 等。
  • handler:饱和策略,当线程池和等待队列都满时,任务提交失败时所使用的处理策略。
  • threadFactory:用于创建新线程的工厂,允许自定义线程的创建,例如设置线程名称或优先级。

注意:前面五个参数是必须指定的,而最后两个参数可以不指定,因为有默认值。

三大核心参数:核心线程用于保证快速响应和资源复用,而非核心线程用于应对突发情况,等待队列则是作为一个缓冲,同时控制处理任务的数量和顺序。

等待队列

默认

  • LinkedBlockingQueue:一个基于链表的阻塞队列,支持 FIFO(先进先出)排序。可以设置容量,默认是 Integer.MAX_VALUE。适用于任务较多且队列可以扩展的场景。
  • ArrayBlockingQueue:一个基于数组的阻塞队列,容量固定,适用于任务数量可预估的场景。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作都必须等待另一个线程的对应移除操作,适合高并发场景
  • PriorityBlockingQueue:支持优先级排序的阻塞队列,适合需要按优先级处理任务的场景。

ArrayBlockingQueue和带有容量的LinkedBlockingQueue有什么区别,怎么选择?

LinkedBlockingQueue插入删除方便,对高并发场景友好,而ArrayBlockingQueue是用循环数组实现,插入删除的代价比较大,涉及到模运算(取余)。

ArrayBlockingQueue内存占用更小,因为是基于数组实现。更适合生产者-消费者模型中的任务数量相对固定的场景。

SynchronousQueue队列特殊在哪?什么时候用?

特殊之处:

  • SynchronousQueue` 的容量为 0,这意味着它不存储任何元素。每个插入操作必须等待一个对应的移除操作才能完成,反之亦然。
  • 直接交付:当一个线程试图插入一个元素到 SynchronousQueue 时,它会被阻塞,直到另一个线程尝试移除这个元素。这种设计实现了线程之间的直接交付。
  • 没有内部缓冲:与其他阻塞队列(如 ArrayBlockingQueueLinkedBlockingQueue)不同,SynchronousQueue 不维护任何内部元素,因此不能进行批量处理或存储任务。

使用效果:当核心线程满了的时候,每提交一个任务,的认为阻塞队列是满的,当非核心线程充足时,会马上创建一个非核心线程去执行这个任务。(内部的CachedThreadPool就是这样)

适用场景:在需要低延迟和高并发的应用场景中,SynchronousQueue 可以减少任务的等待时间,因为生产者和消费者必须在同一时间交互。

拒绝策略

当线程池和等待队列都满时,可以选择不同的拒绝策略对新任务进行处理:

  1. AbortPolicy(默认):拒绝任务并且抛出 RejectedExecutionException,适合希望知道任务失败的场景。
  2. DiscardPolicy:丢弃任务,不抛出异常,适合对任务丢失不敏感的场景。
  3. CallerRunsPolicy:将任务交给线程池的调用者(比如主线程或者其它不属于本线程池管理的线程)自己去执行,适合希望减轻线程池压力的场景。
  4. DiscardOldestPolicy:丢弃掉等待队列中队头的任务,也就是最旧的任务,然后加入等待队列。适合希望保留最新任务的场景。
  5. DelayQueue:一个支持延迟处理的阻塞队列,任务必须在指定延迟后才能获取,适合需要定时执行的任务场景。
  6. 自定义拒绝策略,实现RejectedExecutionHandler接口。

注意:如上队列都属于阻塞队列,能够保证线程安全,并且符合生产者消费者模式,队列满的时候,会阻塞入队操作;队列空的时候,会阻塞出队操作。

自定义拒绝策略可以有哪些思路?

拒绝任务,并且打印一些有用日志。

import java.util.logging.Logger;public class LoggingRejectPolicy implements RejectedExecutionHandler {private static final Logger logger = Logger.getLogger(LoggingRejectPolicy.class.getName());@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {logger.warning("Task " + r.toString() + " rejected. Current pool size: " + executor.getPoolSize());}
}

等待一段时间后再尝试提交任务。

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;public class RateLimitPolicy implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {try {Thread.sleep(100); // 限流,稍等后重试executor.execute(r);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

线程工厂

线程工程用于控制线程池中的线程是如何创建的,也就是给各个程取一个名字。

这个参数一般不需要传递,因为有一个Executors有一个默认的线程工厂(是静态类,所有线程池共用)

默认规则:pool-线程池序号-thread-线程序号

实现逻辑:执行一次DefaultThreadFactory构造方法,线程池的序号就+1(原子操作),而且这个序号是static修饰,所有工厂对象共享;然后每调用一次newThread,线程的序号就+1(原子操作),这个序号不是static,各个工厂都从1开始

    /*** The default thread factory*/static class DefaultThreadFactory implements ThreadFactory {//原子类,static,记录线程池的个数private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;//原子类,记录某个线程的个数private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "pool-" +poolNumber.getAndIncrement() +"-thread-";}//创建线程的京具体方法public Thread newThread(Runnable r) {//拼接出线程的名字Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon())//设置线程都是飞守护线程,不会随着主线程终止而终止t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}

自定义线程工厂实例

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;public class Main {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),//自定义线程工厂new ThreadFactory() {AtomicInteger number = new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {return new Thread(r,"myThreadPool_"+number.getAndIncrement());}});// 提交多个任务for (int i = 0; i < 10; i++) {int taskId = i;threadPoolExecutor.execute(() -> {System.out.println("任务 " + taskId + " 在 " + Thread.currentThread().getName() + " 执行");try {Thread.sleep(5000); // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池threadPoolExecutor.shutdown();}
}
```在这里插入图片描述### 线程池大小该如何设置?假设核心数为n,可参考公式:CPU密集类型(IO比较少),可以设置n+1IO密集类型(读取文件或者数据库的IO比较多),可以设置为线程的k的倍数。比如从2 * n开始,慢慢尝试4 * n、8 * n ... 至于k取多少,可以根据如下公式确定:![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6b85ef0bdffa4153a660e6986ee6c85d.png)> cpu密集型任务为什么设置n+1?当某个线程故障或者缺页中断了,此时剩余一个线程就可以顶上> IO密集类型为什么设置n的整数倍?**因为对于IO密集类型的任务来说,CPU不是一直在运行的,可能在等待**比如CPU运行1秒,IO运行3秒,此时完全可以设置4个线程去执行任务,让3s的等待时间也利用起来。> Windows电脑假设是4核心,8个逻辑处理器,那么最多支持8个线程并发执行吗?为什么java中可以创建100个线程同时执行?不是,八个逻辑处理器意味着最多可以有八个线程**并行执行**(同一时刻),而创建的100个线程会在这八个逻辑处理器中进行并发执行(不断切换)。操作系统会通过上下文切换在不同线程之间切换,从而有效利用处理器资源。虽然同时运行的线程数有限,但通过快速切换,多个线程仍能“看起来”像是在同时执行。> 如果cpu只有一个核心,可以支持多线程吗?就算只有一个核心(假设逻辑处理器也是1),也是可以支持多个线程的,只不过是并发执行,线程数量越多,各个线程的等待其它线程时间片的时间就越长,性能开销就越大。

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

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

相关文章

C++速通LeetCode中等第14题-旋转图像

思路图解&#xff1a; class Solution { public:void rotate(vector<vector<int>>& matrix) {// 设矩阵行列数为 nint n matrix.size();// 起始点范围为 0 < i < n / 2 , 0 < j < (n 1) / 2// 其中 / 为整数除法for (int i 0; i < n / 2; i)…

传知代码-多示例AI模型实现病理图像分类

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 本文将基于多示例深度学习EPLA模型实现对乳腺癌数据集BreaKHis_v1的分类。EPLA模型是处理组织病理学图像的经典之作。EPLA模型是基于多示例学习来进行了&#xff0c;那么多示例学习模型对处理病理学图像具有…

滚动条指定距离滚动

/*** scroller 滚动条元素* to 滚动到位置* duration 滚动时间*/ function scrollLeftTo (scroller, to, duration) {let rafIdlet count 0const from scroller.scrollLeftconst frames duration 0 ? 1 : Math.round((duration * 1000) / 16)function cancel () {cancelAn…

中间件知识点-消息中间件(Kafka)二

Kafka 一、Kafka介绍及基本原理 kafka是一个分布式的、支持分区的、多副本、基于zookeeper的分布式消息系统/中间件。 kafka一般不会删除消息&#xff0c;不管这些消息有没有被消费。只会根据配置的日志保留时间(log.retention.hours)确认消息多久被删除&#xff0c;默认保留…

Navicat数据库管理工具实现Excel、CSV文件导入到MySQL数据库

1.所需要的工具和环境 navicat等第三方数据库管理工具云服务器中安装了 1Panel面板搭建的mysql数据库 2.基于 1Panel启动mysql容器 2.1 环境要求 安装前请确保您的系统符合安装条件&#xff1a; 操作系统&#xff1a;支持主流 Linux 发行版本&#xff08;基于 Debian / Re…

【Python机器学习】NLP信息提取——提取人物/事物关系

目录 词性标注 实体名称标准化 实体关系标准化和提取 单词模式 文本分割 断句 断句的方式 使用正则表达式进行断句 词性标注 词性&#xff08;POS&#xff09;标注可以使用语言模型来完成&#xff0c;这个语言模型包含词及其所有可能词性组成的字典。然后&#xff0c;该…

Jboss Administration Console弱⼝令

漏洞描述 Administration Console管理⻚⾯存在弱⼝令&#xff0c;admin:admin&#xff0c;登陆后台上传war包 , getshell 影响版本 全版本 环境搭建 因为这⾥⽤的环境是CVE-2017-12149的靶机 cd vulhub-master/jboss/CVE-2017-12149 docker-compose up -d 密码⽂件 /j…

开发易忽视的问题:InnoDB 行锁设计与实现

开发易忽视的问题&#xff1a;InnoDB 行锁设计与实现 存储模型和锁机制 存储结构 数据页&#xff1a; InnoDB 将表的数据存储在数据页中&#xff0c;每个页默认大小为 16KB。数据页中存储多个行记录&#xff0c;行记录按照主键顺序存放。 行格式&#xff1a; InnoDB 支持多种…

VSCode开发ros程序无法智能提示的解决方法(二)

VSCode开发ros程序无法智能提示的解决方法&#xff08;二&#xff09; 说明解决 说明 在Ubuntu下使用vscode开发ros程序&#xff0c;无法进行智能提示。 解决 将C/C更换为v1.20.5版本&#xff0c;如下图

sheng的学习笔记-AI-强化学习(Reinforcement Learning, RL)

AI目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 基础知识 什么是强化学习 强化学习&#xff08;Reinforcement Learning, RL&#xff09;&#xff0c;又称再励学习、评价学习或增强学习&#xff0c;是机器学习的范式和方法论之一&#xff0c;用于描述和解决智能体&#…

Trainer API训练属于自己行业的本地大语言模型 医疗本地问答大模型示例

Trainer API 是 Hugging Face transformers 库中强大而灵活的工具&#xff0c;简化了深度学习模型的训练和评估过程。通过提供高层次的接口和多种功能&#xff0c;Trainer API 使研究人员和开发者能够更快地构建和优化自然语言处理模型 文章目录 前言一、Trainer API它能做什么…

RNN的反向传播

目录 1.RNN网络&#xff1a;通过时间反向传播(through time back propagate TTBP) 2.RNN梯度分析 2.1隐藏状态和输出 2.2正向传播&#xff1a; 2.3反向传播&#xff1a; 2.4问题瓶颈&#xff1a; 3.截断时间步分类&#xff1a; 4.截断策略比较 5.反向传播的细节 ​编辑…

达梦数据库踩坑

提示&#xff1a;第一次接触达梦&#xff0c;是真的不好用&#xff0c;各种报错不提示详细信息&#xff0c;吐槽归吐槽&#xff0c;还是需要学习使用的。 前言 题主刚接触达梦数据库时&#xff0c;本来是想下载官网的连接工具进行数据库连接的&#xff0c;但是谁曾想&#xff…

监控易监测对象及指标之:全面监控GBase数据库

在数字化时代&#xff0c;数据库作为企业核心数据资产的管理中心&#xff0c;其稳定性和性能直接关系到业务的连续性和企业的运营效率。GBase数据库作为高性能的分布式数据库系统&#xff0c;广泛应用于各类业务场景。为了确保GBase数据库的稳定运行和高效性能&#xff0c;对其…

git安装包夸克网盘下载

git安装包夸克网盘下载 git夸克网盘 git网站上的安装包下载速度有点慢&#xff0c;因此为了方便以后下载就将文件保存到夸克网盘上&#xff0c;链接&#xff1a;我用夸克网盘分享了「git」&#xff0c;点击链接即可保存。 链接&#xff1a;https://pan.quark.cn/s/07c73c4a30…

C++速通LeetCode中等第12题-矩阵置零(空间O(1)含注释)

class Solution { public:void setZeroes(vector<vector<int>>& matrix) {int m matrix.size();int n matrix[0].size();int flag_col0 false, flag_row0 false;//先记录第一行和第一列是否有零for (int i 0; i < m; i) {if (!matrix[i][0]) {flag_col…

基于单片机的智能健康水杯设计

摘要&#xff1a;随着时代的发展&#xff0c;单片机领域不断扩张。人工智能产品的出现改变了人们的生活方式。智能产品不仅加快了人们的生活节奏&#xff0c;还为人们的安全提供了保障。在快节奏生活的同时&#xff0c;人们开始越来越关注自己的身体健康&#xff0c;基于 52 单…

高级java每日一道面试题-2024年9月20日-分布式篇-什么是CAP理论?

如果有遗漏,评论区告诉我进行补充 面试官: 什么是CAP理论&#xff1f; 我回答: 在Java高级面试中&#xff0c;CAP理论是一个经常被提及的重要概念&#xff0c;它对于理解分布式系统的设计和优化至关重要。CAP理论是分布式系统理论中的一个重要概念&#xff0c;它描述了一个分…

c++11右值引用和移动语义

一.左值引用和右值引用 什么是左值引用&#xff0c;什么是右值引用 左值是一个表示数据的表达式&#xff08;变量名解引用的指针&#xff09;&#xff0c;我们可以获取到它的地址&#xff0c;可以对它赋值&#xff0c;左值可以出现在符号的左边。使用const修饰后&#xff0c;…

通威股份半年报业绩巨降:销售费用大增,近一年股价跌四成

《港湾商业观察》施子夫 王璐 光伏领域龙头企业通威股份&#xff08;600438.SH&#xff09;交出的半年报延续了2023年营收和净利润双下滑趋势&#xff0c;幅度显得更大。 即便受行业波动影响&#xff0c;但如何重整及提升盈利能力&#xff0c;通威股份还需要给出解决方案。​…