【JUC基础】12. 线程池(一)

news2024/12/23 14:40:17

1、前言

我们知道多线程的使用,是为了最大限度发挥现代多核处理器的计算能力,提高系统的吞吐量和性能。但是如果不加以控制和管理,随意使用多线程,对系统性能反而会有不利的影响。线程数量和系统CPU资源是息息相关的,随意使用甚至可能会耗尽系统CPU资源和内存资源。

2、池化技术

为了应对多线程管理和控制的问题,引入池化技术。所谓池化技术,顾名思义就是造一个池子,讲需要管理的东西交给池子管理,而用完之后就放回池子。就像小孩子的玩具收纳箱,玩玩具的时候从收纳箱中拿出玩具,玩完之后一定要教小孩子将玩具放回玩具收纳箱中。池化技术通过优化资源的分配效率,从来达到性能的调优。

其实在Java编程中,池化技术不仅仅在多线程中使用这种方式,其他地方也同样用到了池化技术。如:数据连接池,对象池,内存池等等。

3、为什么要用线程池

前面基础多少讲到了为什么使用线程池的原因。这里详细说明以下几个原因,由ChatGPT来总结一下:

在多线程编程中,频繁地创建和销毁线程是一项昂贵的操作。因此,使用线程池来管理线程的创建、复用和销毁是一种有效的方式。 以下是几个原因解释为什么要使用线程池:

  1. 降低资源消耗:线程的创建和销毁需要消耗系统资源,如内存和CPU。使用线程池可以重用线程,避免频繁创建和销毁线程,从而降低了资源消耗。
  2. 提高系统响应性:线程池能够提高系统的并发能力和响应性。通过合理地配置线程池的大小,可以同时执行多个任务,提高系统的吞吐量和响应时间。
  3. 任务调度和线程复用:线程池可以管理和调度任务的执行。它维护一组线程,可以根据任务的到达顺序和优先级来选择合适的线程执行任务,避免任务争抢和冲突。同时,线程池中的线程可以被重复利用来执行多个任务,避免了频繁创建线程的开销。
  4. 控制并发线程数量:通过设置线程池的大小和任务队列的容量,可以限制并发执行的线程数量,防止系统资源被过度占用,从而提高系统的稳定性和可靠性。
  5. 简化线程编程:使用线程池可以将任务的提交和执行解耦,简化了线程编程的复杂性。开发人员只需关注任务的实现和提交,无需手动创建和管理线程,从而降低了出错的概率。

3.1、线程池优点

线程池优点很明显,上面提到为什么要使用线程池的几个原因就是对应的优点,这里不赘述。

3.2、线程池缺点

线程池的缺点也很明显:

  1. 资源占用:线程池本身会占用一定的系统资源,包括内存和CPU。如果线程池的大小设置不合理,可能会导致资源浪费或不足的问题。
  2. 线程泄露:如果没有正确地关闭线程池,或者任务执行过程中出现异常导致线程无法正常释放,可能会导致线程泄露,进而影响系统性能。
  3. 需要合理配置:线程池的性能和效果受到配置参数的影响,需要根据具体场景合理配置线程池的大小、任务队列的容量等参数,否则可能会影响系统的性能和响应性。
  4. 难以处理长时间任务:线程池主要适用于短时间的任务处理,如果任务执行时间过长,可能会导致线程池中的线程被长时间占用,影响其他任务的执行。

4、如何使用线程池

最简单的线程池使用方法:

public class ThreadPoolTest {

    public static void main(String[] args) {
        // 创建一个固定大小的线程池,大小为3
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交任务给线程池执行
        for (int i = 0; i < 10; i++) {
            // 执行提交任务
            executorService.execute(() -> {
                // ......
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}
  1. 通过Executors.newFixedThreadPool(3)方法创建了一个线程池,该线程池固定线程数量为3;
  2. 使用executorService.execute()方法执行向线程池内提交的线程任务;
  3. 执行完后,通过executorService.shutdown();关闭线程池资源;

通过简单的线程池使用方式,我们就完成了基本的线程池操作。线程池会自动管理线程的创建和销毁,以及任务的调度和执行,帮我们简化了多线程编程的复杂性。

5、JUC线程池

5.1、Executor

Executor 线程池顶级接口,类似一个线程池工厂。接口中只有一个execute()方法,接收Runnable类型。注意这里返回值类型是void。

 

5.2、ExecutorService

ExecutorService继承自Executor接口,添加了关闭线程池以及等待中断等方法。同时添加了submit来提交线程任务,除了接收Runnable以外,还可以接收Callable类型,也增加了返回值。

5.3、AbstractExecutorService

AbstractExecutorService是实现ExecutorService接口的抽象类。默认实现了个别如submit方法等。

5.4、ScheduledExecutorService

该类是为了实现带有定时器功能的线程池。ScheduledExecutorService也是一个接口。包含了定时和延迟处理的方法。

5.5、ThreadPoolExecutor方法参数

ThreadPoolExecutor重点看这个类。ThreadPoolExecutor是JUC中提供的默认线程池实现类。提供了丰富的配置选项和线程池管理功能。

提供了4个可选配置的构造函数:

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

我们重点关注其中的几个参数:corePoolSize,maximumPoolSize,keepAliveTime,workQueue,threadFactory,handler。

5.5.1、corePoolSize

核心线程数。指线程池中始终保持的线程数量,就算他们处于空闲状态,也不会被销毁。而一直存活的最小线程数量。

5.5.2、maximumPoolSize

最大线程数。指线程池中允许的最大线程数量。当maximumPoolSize的数量大于corePoolSize时,多的那部分空闲状态下的线程,会再超过一定时间后被销毁,只保留corePoolSize的核心线程数。

5.5.3、keepAliveTime

非核心线程保持空闲的时间。如果超过这个时间,多的那部分空闲状态下的线程就会被销毁。可以通过unit设置时间单位。

5.5.4、BlockingQueue

任务队列,阻塞队列。当前并发执行的线程数与系统资源有关。当你设置了大于当前系统可负载的线程数量时,多的那部分自然要进行等待,从而进入等待队列。

当线程池中线程数量达到corePoolSize时,且都处于运行状态,这时候后续提交的线程任务会进入到缓存阻塞队列中,等待执行。这个缓存阻塞队列也就是workQueue。

JUC中提供的BlockingQueue有以下几种:

  1. ArrayBlockingQueue:由数组实现的有界阻塞队列。需要指定队列的容量大小。当队列已满时,添加任务的操作将被阻塞,直到队列中有空位。ArrayBlockingQueue适用于固定大小的线程池,可以控制线程池中的最大任务数。
  2. LinkedBlockingQueue:由链表实现的可选有界或无界阻塞队列。如果创建LinkedBlockingQueue时没有指定容量大小,那么它将是一个无界队列,可以无限制地添加任务。如果指定了容量大小,它将成为一个有界队列。当队列已满时,添加任务的操作将被阻塞。LinkedBlockingQueue适用于任务数比较大且变化较大的场景。
  3. SynchronousQueue:一个没有缓冲区的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。SynchronousQueue适用于任务直接交付给线程执行的场景,可以有效地避免任务的排队和缓冲。
  4. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。元素按照比较器或元素的自然顺序进行排序。PriorityBlockingQueue适用于需要按照优先级顺序处理任务的场景。

注:当使用了无界队列后,maximumPoolSize会失效。

这些BlockingQueue的区别主要在于容量限制、阻塞特性和元素排序。根据具体的需求和场景,选择合适的BlockingQueue可以提高线程池的性能和效率。

5.5.5、threadFactory

线程工厂。用于创建线程的工厂类。可以通过设置线程工厂来自定义线程的创建方式,例如设置线程名称、线程优先级等

5.5.6、RejectedExecutionHandler

拒绝策略。用于处理无法接收的任务。当线程池已满且任务无法提交时,会触发拒绝策略来处理这些任务。

JUC提供的RejectedExecutionHandler有以下几种:

  1. AbortPolicy(默认策略):该策略会直接抛出RejectedExecutionException异常,阻止任务的提交。
  2. CallerRunsPolicy:当线程池无法接收任务时,会将任务返回给调用者执行。也就是说,由提交任务的线程来执行该任务。这样可以降低任务提交速度,但可能会影响调用线程的性能。
  3. DiscardPolicy:该策略会默默丢弃无法接收的任务,没有任何提示和异常。这可能导致任务的丢失,潜在的风险需要注意。
  4. DiscardOldestPolicy:当线程池无法接收任务时,会丢弃队列中最旧的任务,然后尝试再次提交任务。这样可以保留较新的任务,但可能会丢失一些较旧的任务。

这些拒绝策略在处理无法接收的任务时具有不同的行为,可以根据具体的需求和业务场景选择合适的策略。需要根据任务的重要性、丢失任务的风险以及业务需求来综合考虑选择合适的拒绝策略。

5.6、手动创建一个线程池

private final static ThreadPoolExecutor threadPoolExecutor;

static {
    // 这里利用hutool提供的ThreadFactoryBuilder,创建一个线程池工厂,并配置线程名称前缀
    // ThreadFactory是个接口,也可以自定义实现
    ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNamePrefix("common-thread-pool-").build();
    threadPoolExecutor = new ThreadPoolExecutor(

            // 核心线程数为7,
            // 通常IO密集型的可以配置为2 * cpu数量
            // CPU密集型的可以配置为 cpu数量 + 1
            7,
            
            // 最大线程数量20个
            20,
            
            // 空闲等待时间,1分钟
            // 超过1分钟,多余的空闲线程会被销毁
            1 * 60,
            
            // 空闲等待时间,单位
            TimeUnit.SECONDS,
            
            // 有界等待队列,固定长度为50
            // 如果使用无界队列,需要考虑内存占用问题
            new ArrayBlockingQueue(50),
            
            // 线程工厂
            threadFactory,
            
            // 拒绝策略
            new ThreadPoolExecutor.AbortPolicy());
}

6、小结

到这里,基本交代了线程池的一些基础概念,以及关于线程池的一些基础使用。后面的章节会讲到线程池的几个实现类,以及简单的场景使用案例。

持续更新中......

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

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

相关文章

FPGA纯vhdl实现XGMII接口10G万兆网UDP协议 配合10G Ethernet PCS/PMA使用 提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、详细设计方案本 10G-UDP 协议栈功能和性能描述本 10G-UDP 协议栈设计框图用户发送AXIS接口描述用户接收AXIS接口描述控制接口描述XGMII接口描述 4、vivado工程详解10G-UDP协议栈10G Ethernet PCS/PMA IP核 5、上板调试验证并演示6、福利&…

自动缩放Kubernetes上的Kinesis Data Streams应用程序

想要学习如何在Kubernetes上自动缩放您的Kinesis Data Streams消费者应用程序&#xff0c;以便节省成本并提高资源效率吗&#xff1f;本文提供了一个逐步指南&#xff0c;教您如何实现这一目标。 通过利用Kubernetes对Kinesis消费者应用程序进行自动缩放&#xff0c;您可以从其…

nps与npc内网穿透搭建

1.简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协议&#xff0c;支持内网http代理、内网socks5代理、p2p等&#xff0c;并带有功能强大的web管理端。 使用内网穿透技术可以使你在公共网络环境也能…

第二章 部署DNS服务

♥️作者介绍&#xff1a;奇妙的大歪 ♥️个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01; ♥️个人简介&#xff1a;云计算网络运维专业人员 目录 一.DNS是什么&#xff1f; 1.DNS的工作原理 二.DNS内容 1.本章单词 2.域名空间结构&#xff1a; 3.顶级域…

企业级开发1.6 JdbcTemplate操作

JdbcTemplate操作 一、JdbcTemplate案例演示&#xff08;一&#xff09;创建数据库与表1、创建数据库2、创建用户表3、用户表添加记录4、查看用户表内容 &#xff08;二&#xff09;打开Spring项目&#xff08;三&#xff09;添加数据库相关依赖&#xff08;四&#xff09;创建…

李彦宏疯了?百度要把全部产品重做一遍

大家好&#xff0c;我是校长。 前几天李彦宏参加了在北京举办的 2023 中关村论坛&#xff0c;发表了题为《大模型改变世界》的演讲。 聊到了很多有意思的观点&#xff0c;分享几个&#xff0c;我们一起看看。 1、大模型重新定义了人机交互。 李彦宏说&#xff1a;过去几十年&am…

playwright - 剧作家, 端对端测试

本文基于 playwright v1.34.3, node v18.16.0 注意&#xff1a;playwright 版本随 node lts 版本更新而更新&#xff0c;所以请确保 node 版本与 playwright 版本匹配。 写在前面 前端开发阶段&#xff0c;少不了测试&#xff0c;一般包含两类测试&#xff1a;单元测试、端对端…

chatgpt赋能python:Python中输入怎么写?完整教程

Python中输入怎么写&#xff1f;完整教程 如果你正在学习Python编程&#xff0c;那么输入是你必须掌握的重要概念之一。在Python中&#xff0c;输入是指将数据从用户的键盘输入到程序中。这些数据可以是字符串、整数、浮点数或其他任何类型的值。本文将提供有关Python中输入的…

外贸大环境下soho人策略

前阵子也跟一个工厂在聊&#xff0c;现在普遍毛利率只有5%-6%&#xff0c;根本不敢涨价&#xff0c;能不降价就不错了&#xff08;汇率在涨&#xff0c;所以有的客户还会要求降价&#xff09;。 卷是一定的。而且&#xff0c;各位如果有了解过拼多多的TEMU业务的&#xff0c;应…

1727_使用虚拟机安装CentOS-7

全部学习汇总&#xff1a;GreyZhang/little_bits_of_linux: My notes on the trip of learning linux. (github.com) 离开Linux很久了&#xff0c;甚至怀念&#xff0c;虚拟机里装个CentOS 7玩玩。使用的是VM虚拟机&#xff0c;下载了CentOS 7的everything安装包。 1&#xf…

chatgpt赋能python:Python中符号怎么输入

Python中符号怎么输入 如果你是一位Python程序员&#xff0c;你肯定会经常使用各种符号&#xff0c;比如冒号、逗号、括号、引号等等。但是有些符号在输入的时候可能会有一些困难&#xff0c;尤其是对于初学者而言。那么在Python中符号怎么输入呢&#xff1f;下面我们来详细介…

4.1 文件操作(File类)

ava中&#xff0c;对文件操作的常用类是java.io.File。这个类提供了许多方法来操作文件和目录。本章节我们将学习关于File类的重要方法以及如何使用它们来操作文件。 4.1.1 创建File对象 创建一个File对象不会在磁盘上创建一个新文件。File对象只是一个在Java代码中表示文件或…

代码覆盖率

在做单元测试时&#xff0c;代码覆盖率常常被拿来作为衡量测试好坏的指标&#xff0c;甚至&#xff0c;用代码覆盖率来考核测试任务完成情况&#xff0c;比如&#xff0c;代码覆盖率必须达到80&#xff05;或 90&#xff05;。于是乎&#xff0c;测试人员费尽心思设计案例覆盖代…

【30天熟悉Go语言】4 Go的变量、常量、运算符

文章目录 一、前言二、变量1、变量的基础使用2、变量的多种使用方式1&#xff09;全局变量2&#xff09;局部变量3&#xff09;丢弃赋值 3、Go和Java的变量对比 三、常量1、Go和Java的常量对比 三、运算符1、算术运算符 、--2、运算符 &、* 四、总结 一、前言 Go系列文章&a…

计算机组成原理 之 第四章 指令系统

1. 指令格式 通常包括操作码字段&#xff08;OP&#xff09;和地址码字段&#xff08;A&#xff09;&#xff0c;有的指令不需要地址码 指令系统&#xff08;指令集&#xff09;&#xff1a;一台计算机的所有指令的集合&#xff0c;eg&#xff1a;X86、ARM &#xff08;1&#…

手机安卓Termux搭建Hexo博客网站,发布公网访问

文章目录 1. 安装 Hexo2. 安装cpolar内网穿透3. 公网远程访问4. 固定公网地址 转载自cpolar极点云的文章&#xff1a;安卓手机使用Termux搭建Hexo个人博客网站【内网穿透公网访问】 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章&#…

一键搭建本地Wordpress环境 - MacOS

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

聊聊企业无线网络安全

新钛云服已累计为您分享749篇技术干货 不知不觉无线网络已经成为了办公网主流。最早接触无线网络的时候是2001年&#xff0c;那时候笔记本电脑还比较少见&#xff0c;标配也不支持无线网络&#xff0c;要使用无线网络需要另外加一块PCMIA接口的无线网卡。第一次体验无线网络的时…

【k8s】Jenkins实现springcloud应用CI、CD实践 【三】

一、运行Jenkins流水线流程思路&#xff1a; 场景&#xff1a;java微服务应用&#xff0c; 单体仓库&#xff0c;多个微服务模块&#xff0c;&#xff08;并行构建、自动化构建、根据模块变更仅更新特定模块&#xff09; java、nodejsCI阶段 并行方式; 根据模块变…

【AUTOSAR】Com通讯栈配置说明(三)---- CanSM模块

CanNm模块 该项目中的Nm 并非 autosar nm&#xff0c; 不适用ETAS 配置 CanSM CanSMConfiguration CanSMModeRequestRepetitionMax: 模式请求失败后最大的重试次数 CanSMModeRequestRepetitionTime&#xff1a;模式请求重试时间间隔 CanSMManagerNetworks CanSMBorCounterL1…