【Java】线程池技术(二)ThreadPoolExecutor的基本定义

线程池初始化与定义

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, 
                          long keepAliveTime, TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

线程池构造方法的入参含义分别如下:

  • corePoolSize:核心线程数,必须大于或等于 0
  • maximumPoolSize:最大线程数,必须大于 0 且大于或等于核心线程数
  • keepAliveTime:空闲线程存活时间,必须大于或等于 0
  • unit:存活时间单位 TimeUnit
  • workQueue:阻塞队列,存储通过 execute() 方法提交的未能开始执行的任务。常见的选择有:
    • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO
    • PriorityBlockingQueue:具有优先级的无界阻塞队列,较少使用
    • LinkedBlockingQueue:基于链表结构的无界阻塞队列,吞吐量通常要高于 ArrayBlockingQueue,FIFO
    • SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue
  • threadFactory:线程创建工厂,一般可以用默认的
  • handler:饱和/拒绝策略,有以下四种策略:
    • ThreadPoolExecutor.AbortPolicy:(默认)阻塞队列已满,丢弃任务并抛出 RejectedExecutionException 异常。
    • ThreadPoolExecutor.DiscardPolicy:阻塞队列已满,丢弃任务,但是不抛出异常。
    • ThreadPoolExecutor.DiscardOldestPolicy:阻塞队列已满,丢弃队列最前面的任务,然后重新尝试加入队列执行任务(重复此过程)。
    • ThreadPoolExecutor.CallerRunsPolicy:由发起调用的线程自己去执行该任务(如果主线程运行结束,则丢弃该任务);会降低新任务的提交速度,影响程序的整体性能。
    • 也可以根据实际需求自定义拒绝策略,实现 RejectedExecutionHandler 接口

当 ThreadPoolExecutor 线程池被创建的时候,里面是没有创建工作线程的,直至有任务调用了 execute() 方法时,才开始创建工作线程。除非调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,可以手动预创建线程。调用 execute() 方法时的具体工作原理为:

  1. 如果当前工作线程数小于核心线程数,则创建新的线程执行任务,否则将任务加入阻塞队列;
  2. 如果阻塞队列满了,则根据最大线程数创建额外(非核心工作线程)的工作线程去执行任务;
  3. 如果工作线程数达到了线程池允许的最大线程数,则根据拒绝策略去执行。
  4. 非核心线程的存活时间到期的话,线程资源将会被回收。

核心线程在线程池刚创建的时候还未被创建,随着任务的执行才会创建新的核心线程。线程池启动使用后,核心线程默认不会被回收,除非通过方法 allowCoreThreadTimeOut(boolean value) 启用了空闲核心线程回收,此时 keepAliveTime 才会对核心线程也生效。调用该方法启动空闲核心线程回收时,会马上执行一次回收的操作。

除了默认的线程池 ThreadPoolExecutor() 以外,Java 还通过 Executors 定义了四种线程池:

// 1. CachedThreadPool 有缓冲的线程池,具体线程数由JVM控制,灵活创建于回收线程资源,适用于并发执行大量短期的小任务
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

// 2. FixedThreadPool 固定大小的线程池(所有线程都是核心线程),适用于处理CPU密集型的任务
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

// 3. ScheduledThreadPool 定时执行任务的线程池,内部使用延时队列存储阻塞任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

// 4. SingleThreadExecutor 单线程的线程池,只有一个线程在工作,适用于串行执行任务的场景
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
        );
}

其中,newScheduledThreadPool 的具体实现为:

// ScheduledThreadPoolExecutor.java
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
    
// ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}

本质上,以上的各类线程池还是通过 ThreadPoolExecutor 类实现的初始化。但是,在阿里巴巴 Java 开发手册中明确指出,不允许使用 Executors 创建线程池:

img

从上面 FixedThreadPool 和 SingleThreadPool 的构造函数可以得知,初始化 LinkedBlockingQueue 时没有指定其容量。LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,默认最大长度为 Integer.MAX_VALUE。对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。

CachedThreadPool 和 ScheduledThreadPool 这两个线程池虽然没有这种无边界的阻塞队列的情况,但这两种线程池可达到的最大线程数可能是 Integer.MAX_VALUE ,而创建这么多线程,就有非常大的概率导致 OOM。

为了避免出现以上问题,一个直接的思路就是避免使用其默认的构造实现。我们可以根据实际应用场景,通过自定义构造参数,直接调用 ThreadPoolExecutor 的构造方法来创建线程池。初始化阻塞队列时,明确指定队列大小即可。

当实际提交的线程数超过了当前允许的最大线程数时,就会抛出 java.util.concurrent.RejectedExecutionException ,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是抛出异常(Exception)进行捕获总比发生错误(Error)影响程序运行要好。

同时,手动创建线程池还可以指定线程名称,这样便于对线程池的运行情况进行监控和追踪。

综上所属,生产环境中常用的线程池构造方法如下:

  1. 通过 ThreadPoolExecutor 构造函数实现
  2. 通过 Executors 工具类来创建不同类型的 ThreadPoolExecutor 线程池。

其中,更多的推荐使用第一种方法。

线程池参数的配置

线程池线程数大小是一个值得仔细斟酌设置的参数。

如果设置过小,当同一时间出现大量任务需要执行时,可能会导致大量任务在阻塞队列中排队等待,甚至会出现队列满员后任务无法处理的情况,或大量任务堆积导致的 OOM。这种场景下 CPU 资源没有得到充分的利用。

如果设置过大,大量线程可能会同时竞争 CPU 资源,导致大量的上下文切换,从而增加线程的执行时间,影响了整体的执行效率。

线程池参数的选用,大部分都是需要根据实际测试结果去调整得出最佳配置。不过有一些简单的经验值可以参考一下:

  • IO 密集型任务:2N。系统大部分时间都用来处理 IO 交互,期间不会占用 CPU 资源,此时释放出来的 CPU 资源可以用于其他线程执行任务,因此可以尽可能多的配置线程。
  • CPU 密集型任务:N+1。任务中存在大量复杂的运算,消耗的主要是 CPU 资源。多出来的一个线程是为了防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,此时多出来的一个线程就可以将 CPU 空闲时间利用起来。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++的动态内存分配

使用new/delete操作符在堆中分配/释放内存 //使用new操作符在堆中分配内存int* p1 new int;*p1 2234;qDebug() << "数字是&#xff1a;" << *p1;//使用delete操作符在堆中释放内存delete p1;在分配内存的同时初始化 //在分配内存的时初始化int* p2 n…

chatgpt: linux 下用纯c 编写一按钮,当按钮按下在一新窗口显示hello world

用这个程序模板&#xff0c;就可以告别只能在黑框框的终端中编程了。 在 Linux 环境下使用纯 C 语言编写一个按钮&#xff0c;当按钮按下时&#xff0c;在一个新窗口显示 "Hello World"。我们可以使用 GTK 库来实现这个功能。GTK 是一个用于创建图形用户界面的跨平台…

第三十三篇-Ollama+AnythingLLM基本集成

AnythingLLM AnythingLLM专属私有知识库,可以使用本地OllamaLLM模型&#xff0c;可以上传文件&#xff0c;基于文件回答问题 启动ollama 参考 第二十五篇-Ollama-离线安装 第二十四篇-Ollama-在线安装 下载安装AnythingLLM https://useanything.com/downloadAnythingLLMDe…

C#使用NPOI库实现Excel的导入导出操作——提升数据处理效率的利器

文章目录 一、NPOI库简介二、安装与引入三、Excel的导入操作1.CSV格式导入2.XLS格式导入3. XLSX格式导入 四、Excel的导出操作1. CSV格式导出2. XLS格式导出3. XLSX格式导出 五、NPOI库的应用优势与改进方向总结 在日常工作学习中&#xff0c;我们经常需要处理Excel文件&#x…

【吊打面试官系列-Mysql面试题】什么是锁?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是锁&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是锁&#xff1f; 答&#xff1a;数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时&#xff0c;在数据库中就会产生多个事务同时存取同一…

RocketMQ快速入门:集成spring, springboot实现各类消息消费(七)附带源码

0. 引言 rocketmq支持两种消费模式&#xff1a;pull和push&#xff0c;在实际开发中这两种模式分别是如何实现的呢&#xff0c;在spring框架和springboot框架中集成有什么差异&#xff1f;今天我们一起来探究这两个问题。 1. java client实现消息消费 1、添加依赖 <depen…

运维 Tips | IT工程师常用的8个USB引导启动器工具

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 【导语】本指南旨在深入探讨Linux上可用的前六个工具&#xff0c;以及Windows上使用两个U盘启动器生成及刻录工具&#xff0c;创建USB引导启动器用于引导系统ISO文件加载到计算机中&#xff0c;从…

LInux驱动开发笔记(十)SPI子系统及其驱动

文章目录 前言一、SPI驱动框架二、总线驱动2.1 SPI总线的运行机制2.2 重要数据结构2.2.1 spi_controller2.2.2 spi_driver2.2.3 spi_device2.2.4 spi_transfer2.2.5 spi_message 三、设备驱动的编写3.1 设备树的修改3.2 相关API函数3.2.1 spi_setup( )3.2.2 spi_message_init( …

在windows 台式机电脑部署GLM4大模型

参考这篇文章在windows笔记本电脑部署GLM4大模型_16g显卡本地部署glm4-CSDN博客 我的环境&#xff08;PC台式机电脑&#xff1a; 处理器 Intel(R) Core(TM) i9-14900K 3.20 GHz 机带 RAM 32.0 GB (31.8 GB 可用)、32G内存、NVIDIA RTX4080&#xff08;16G&#xff09;…

深入理解Open vSwitch(OVS):原理、架构与操作

一、引言 随着云计算和虚拟化技术的不断发展&#xff0c;网络虚拟化成为了构建灵活、可扩展网络架构的关键技术之一。Open vSwitch&#xff08;OVS&#xff09;作为一种功能强大的开源虚拟交换机&#xff0c;被广泛应用于云计算和虚拟化环境中&#xff0c;为虚拟机提供高效、灵…

前端调试技巧

1、利用console打印日志 2、利用debugger关键字&#xff0c;浏览器f12调用到方法debugger处会断点住&#xff0c;可以利用浏览器调试工具查看变量 a.监视表达式可以添加想要观察的变量 b.调用堆栈可以观察方法调用链 3、xhr断点 请求地址包含v1.0/banner_theme/pagelist&a…

预制舱变电站高压室巡检机器人系统

一、背景 预制舱变电站高压室由于空间狭小、设备紧凑&#xff0c;传统的巡检方式往往需要人工进入高压室进行巡检&#xff0c;不仅存在安全风险&#xff0c;而且巡检效率低下&#xff0c;难以满足日益增长的电力设备运维需求。 二、预制舱高压室巡检机器人系统 预制舱高压室巡…

express+vue在线im实现【四】

往期内容 expressvue在线im实现【一】 expressvue在线im实现【二】 expressvue在线im实现【三】 本期示例 本期总结 支持了音频的录制和发送&#xff0c;如果觉得对你有用&#xff0c;还请点个免费的收藏与关注 下期安排 在线语音 具体实现 <template><kl-dial…

【总结】ui自动化selenium知识点总结

1. 大致原理 首页安装第三方库selenium库&#xff0c; 其次要下载好浏览器驱动文件&#xff0c;比如谷歌的 chromedriver.exe&#xff0c;配置上环境变量。 使用selenium的webdriver类去创建一个浏览器驱动对象赋值叫driver&#xff0c;一个浏览器驱动对象就可以 实现 对浏…

【PHP项目实战训练】——使用thinkphp框架对数据进行增删改查功能

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

国内怎样使用GPT4 turbo

GPT是当前最为熟知的大模型&#xff0c;它优越的性能一直遥遥领先于其它一众厂商&#xff0c;然而如此优秀的AI在中国境内却是无法正常使用的。本文将告诉你4种使用gpt4的方法&#xff0c;让你突破限制顺利使用。 官方售价是20美元/月&#xff0c;40次提问/3小时&#xff0c;需…

嵌入式系统软件开发环境_2.一般架构

1.Eclipse框架 嵌入式系统软件开发环境是可帮助用户开发嵌入式软件的一组工具的集合&#xff0c;其架构的主要特征离不开“集成”问题&#xff0c;采用什么样的架构框架是决定开发环境优劣主要因素。Eclipse框架是当前嵌入式系统软件开发环境被普遍公认的一种基础环境框架。目…

vscode插件开发之 - TestController

TesController概要介绍 TestController 组件是用于实现自定义测试框架和集成测试结果的。它允许开发者定义自己的测试运行器&#xff0c;以支持在VSCode中运行和展示测试。以下是一些使用 TestController 组件的主要场景&#xff1a; 自定义测试框架&#xff1a;如果你正在开发…

深度学习算法informer(时序预测)(三)(Encoder)

一、EncoderLayer架构如图&#xff08;不改变输入形状&#xff09; 二、ConvLayer架构如图&#xff08;输入形状中特征维度减半&#xff09; 三、Encoder整体 包括三部分 1. 多层EncoderLayer 2. 多层ConvLayer 3. 层归一化 代码如下 class AttentionLayer(nn.Module):de…

世界奇观短视频制作,AI加持,新手也能月入上万

在这个数字化的时代&#xff0c;短视频已经成为了人们获取信息和娱乐的重要途径。特别是那些展示世界奇观的短视频&#xff0c;如极端的气候、危险的动物、美丽的自然景观等&#xff0c;这些主题具有很强的吸引力&#xff0c;能够引起观众的兴趣和好奇心。那么&#xff0c;如何…