Java并发编程 —— ThreadPoolExecutor线程池详解

news2024/9/22 11:39:14

一、什么是线程池

线程池是一种池化技术,是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。这样实现线程的复用,避免重复创建与销毁线程的开销和大量线程上下文切换,提高系统效率和并发度。

使用线程池的好处:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

二、线程池类ThreadPoolExecutor

我们可以通过ThreadPoolExecutor类构造函数来创建一个线程池,然后调用threadPoolExecutor.execute(Runnable command)方法提交任务执行。

在这里插入图片描述

1. ThreadPoolExecutor构造函数参数

最多七个参数

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

(1)corePoolSize

最大核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程超时也会回收。

(2)maximumPoolSize

线程池所允许的最大线程数。当等待队列满,就会创建非核心线程来处理,核心线程数+非核心线程数最大为maximumPoolSize。

(3)keepAliveTime

线程闲置超时时长。如果线程闲置时间超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。

(4)timeUnit

keepAliveTime的单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

(5)workQueue

等待线程执行的任务阻塞队列。任务提交执行的流程如下

在这里插入图片描述

任务出队执行的规则依赖于具体的实现类,任务队列的常用实现类有

  • ArrayBlockingQueue :一个数组实现的有界阻塞队列,此队列按照FIFO的原则对元素进行排序,支持公平访问队列。
  • LinkedBlockingQueue :一个由链表结构组成的可选有界阻塞队列,如果不指定大小,则使用Integer.MAX_VALUE作为队列大小,按照FIFO的原则对元素进行排序。
  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列,默认情况下采用自然顺序排列,也可以指定Comparator。
  • DelayQueue:一个支持延时获取元素的无界阻塞队列,创建元素时可以指定多久以后才能从队列中获取当前元素,常用于缓存系统设计与定时任务调度等。
  • SynchronousQueue:一个不存储元素的阻塞队列。存入操作必须等待获取操作,反之亦然。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列,与LinkedBlockingQueue相比多了transfer和tryTranfer方法,该方法在有消费者等待接收元素时会立即将元素传递给消费者。
  • LinkedBlockingDeque:一个由链表结构组成的双端阻塞队列,可以从队列的两端插入和删除元素。

(6)threadFactory

线程创建工厂。用于指定为线程池创建新线程的方式,threadFactory可以设置线程名称、线程组、优先级等参数。可以使用Executors工具类中的方法获取,例如Executors.defaultThreadFactory()

(7)handler

当达到最大线程数且队列任务已满时需要执行的拒绝策略,常见的拒绝策略如下:

  • ThreadPoolExecutor.AbortPolicy:默认策略,当任务队列满时抛出RejectedExecutionException异常。
  • ThreadPoolExecutor.DiscardPolicy:丢弃掉不能执行的新任务,不抛任何异常。
  • ThreadPoolExecutor.CallerRunsPolicy:当任务队列满时使用调用者的线程直接执行该任务。
  • ThreadPoolExecutor.DiscardOldestPolicy:当任务队列满时丢弃阻塞队列头部的任务(即最老的任务),然后添加当前任务。

2. ThreadPoolExecutor线程池状态

ThreadPoolExecutor有一个AtomicInteger类型成员变量ctl,高3位存储线程池状态,低29位存储线程数量。

(1) 线程池的状态说明

线程池有如下五种状态

  • RUNNING:运行状态,线程池可以接收新的任务和执行已入队的任务。
  • SHUTDOWN:线程池处不接收新任务,但不影响正在执行的任务,且能处理已入队的任务。全部处理完毕线程全部回收后进入TIDYING状态。
  • STOP:线程池处不接收新任务,移除已经入队的任务且不处理,同时会中断正在执行的任务。线程全部回收后进入TIDYING状态。
  • TIDYING:线程池中所有的任务已终止,线程数为0;线程池变为TIDYING状态时,会执行钩子函数terminated(),默认实现为空。
  • TERMINATED:钩子函数terminated()被执行完成。

(2)关闭线程池的方法

线程池一旦被创建,就处于RUNNING状态,并且线程池中的初始线程数为0。可调用如下方法关闭线程池

  • shutdown():RUNNING -> SHUTDOWN

  • shutdownNow():直接进入STOP状态。

在这里插入图片描述

3. ThreadPoolExecutor线程复用原理

ThreadPoolExecutor对象的成员变量workers保存了当前所有工作线程,每个工作线程都是封装为了一个内部类Worker的对象

private final HashSet<Worker> workers = new HashSet<Worker>();

当调用execute()方法提交任务时,若线程池还没满,则调用addWorker()方法添加一个线程并启动线程执行Worker的run()方法

在这里插入图片描述
run()方法中执行了runWorker()方法,在执行完firstTask后,会循环通过getTask()方法从等待队列workQueue中取任务执行,以此实现了线程复用。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
		...
        try {
            while (task != null || (task = getTask()) != null) {
				...
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    }
                    ...

三、Executors工具类

Executors工具类中有一些用来创建预定义线程池的方法

  • FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor: 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool :该返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池。

但在《阿里巴巴 Java 开发手册》中并不推荐使用,原因有两点:

  1. 这些预定义的线程池允许的任务队列长度或最大线程数为Integer.MAX_VALUE,可能会导致请求堆积或创建大量线程,从而导致OOM
  2. 通过 ThreadPoolExecutor 的方式创建可以更加明确线程池的运行规则,规避资源耗尽的风险。

四、线程池的线程数设置

线程数是线程池中的重要参数,如果设置小了则不能充分利用CPU,并且会导致大量任务阻塞等待,如果设置大了则会导致大量的线程上下文切换影响整体效率。因此,我们需要根据这个线程池要负责的任务类型来合理设置最大线程数。任务可分为以下两种

1. CPU 密集型任务(N+1)

这种任务消耗的主要是 CPU 资源,几乎不需要阻塞等待获取其他资源,能够充分利用CPU,因此可以将线程数设置为 N(CPU 核心数)+1。比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

2. I/O 密集型任务

这种任务运行时需要进行大量IO操作,而线程在内核处理 I/O 的时间段内不会占用 CPU,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程。具体数量根据任务执行期间平均等待时间来定,需要的IO操作越多等待时间越长,最大线程数就可以设置得越多,以充分利用CPU资源。

例如在Tomcat中处理HTTP请求的线程池默认最大线程数为200,因为HTTP请求处理时通常真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。

线程数更严谨的计算的方法应该是:最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间)),其中 WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)。

参考文章:

  1. Java Guide
  2. https://www.zhihu.com/question/336683528/answer/2518487120
  3. https://blog.csdn.net/BUPTZhanggg/article/details/128214568

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

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

相关文章

司美格鲁肽进入临床竞速期,减肥“神药”生于偶然、火于乱象?

减肥作为一门市场需求旺盛的生意&#xff0c;在很多行业都有所渗透&#xff0c;如今其高潮逐渐来到了医药领域。 CDE&#xff08;国家食品药品监督管理局药品审评中心&#xff09;网站显示&#xff0c;4月17日&#xff0c;联邦制药全资附属公司联邦生物科技&#xff08;珠海横…

Figma导出源文件的方法,用这个方法快速转换其它格式

市场上设计工具层出不穷&#xff0c;Sketch、AdobeXD、Axure、InVision、Figma、Pixso等都是优秀的设计工具&#xff0c;设计师经常面临如何从设计工具中导出文件的问题。 Figma软件的导出功能非常强大&#xff0c;因为轻量化体验受到很多设计师的喜爱。如何保存导出Figma源文…

【Leetcode -19.删除链表的倒数第N个结点 -24.两两交换链表中的节点】

Leetcode Leetcode -19.删除链表的倒数第N个结点Leetcode - 24.两两交换链表中的节点 Leetcode -19.删除链表的倒数第N个结点 题目&#xff1a;给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;he…

软件测试人员在工作中如何运用Linux

从事过软件测试的小伙们就会明白会使用Linux是多么重要的一件事&#xff0c;工作时需要用到&#xff0c;面试时会被问到&#xff0c;简历中需要写到。 对于软件测试人员来说&#xff0c;不需要你多么熟练使用Linux所有命令&#xff0c;也不需要你对Linux系统完全了解&#xff…

vue总线bus的使用和移除注意事项

vue总线bus的使用和移除注意事项 本文目录 vue总线bus的使用和移除注意事项初始化并封装发送事件接收事件移除事件监听实际使用正确测试效果错误测试效果 初始化并封装 在main.js中对bus进行初始化&#xff0c; Bus是一个不具备 DOM 的组件&#xff0c;它具有的仅仅只是它实例…

算法 | 最长回文子串

思路&#xff1a;遍历字符串&#xff0c;对于字符串的每个字符&#xff0c;维持两个游标&#xff08;left&#xff0c;right&#xff09;&#xff0c;找到游标对应字符相等时就同速度向两边扩散。 对于 奇数长度子串&#xff0c;left right 对于偶数成都子串&#xff0c;lef…

RocketMq消息

消息发送 发送同步消息 public class SyncProducer {public static void main(String[] args) throws Exception{DefaultMQProducer producernew DefaultMQProducer(/*please_rename_unique_group_name*/"group1");producer.setNamesrvAddr("localhost:9876&q…

复现Nginx 解析漏洞

目录 漏洞原理 漏洞复现 编译环境 制作图片马 一&#xff1a;随便弄一张图片 二&#xff1a;准备写一个.php文件&#xff0c;写上木马 三&#xff1a;合成图片马 上传图片马 修复漏洞 漏洞原理 1、 由于nginx.conf的如下配置导致nginx把以’.php’结尾的文件交给fast…

一篇文章告诉你金融行业如何高效管理文件

由于金融行业的行业属性&#xff0c;信息安全万分重要。因此在文件管理工具时&#xff0c;要注意数据安全问题&#xff0c;那么金融行业如何高效管理文件呢&#xff1f; 首先金融行业在文件管理时可能面临以下问题&#xff1a; 1&#xff0c;资料繁杂&#xff0c;整理困难&…

Unity Camera -- (3)控制相机能看到的东西

使用剪裁平面&#xff08;Clipping Planes&#xff09;定义相机可视范围 Clipping Planes定义了相机所能看到的场景范围。在有些时候&#xff0c;限制相机所能看到的范围是出于风格上的考量&#xff0c;但编辑Clipping Planes的主要目的是为了优化性能。相机所需要渲染的东西越…

什么是低码平台?低代码平台能解决什么问题?

低代码平台是近年来日益流行的一种新型软件开发工具。它们提供了一种更简单、更快速、更具成本效益的方式来构建和部署定制软件应用程序。在本文中&#xff0c;我们将探讨什么是低码平台&#xff0c;它们可以解决什么问题&#xff0c;以及它们为什么变得如此流行。 一、什么是低…

第十六章 命令模式

文章目录 前言一、命令模式解决智能生活项目设计思想完整代码Command 所有命令的父接口&#xff08;绑定命令都需要实现&#xff09;LightReceiver 命令接受者LightOnCommand / LightOffCommand 将具体的命令进行绑定空命令命令调用者Clint 测试 添加新的命令&#xff0c;非常简…

【信息安全案例】——身份与访问安全(学习笔记)

&#x1f4d6; 前言&#xff1a;一位用户对计算机信息资源的访问活动中&#xff0c;首先必须拥有身份标识&#xff0c;通过该标识鉴别该用户的身份&#xff0c;进一步地&#xff0c;用户还应当具有执行所请求动作的必要权限&#xff0c;系统会验证并控制其能否执行对资源试图完…

Human Pose Regression with Residual Log-likelihood Estimation

Abstract 通过似然热图对输出分布进行建模的基于热图的方法在人体姿态估计领域占据主导地位。相比之下&#xff0c;基于回归的方法更有效&#xff0c;但效果较差。 在这项工作中&#xff0c;我们探索了最大似然估计&#xff08;MLE&#xff09;&#xff0c;以开发一种高效有…

基于朴素贝叶斯的垃圾邮件分类系统项目开发教程

项目资源下载 基于朴素贝叶斯的垃圾邮件分类系统源码 项目简介 本项目基于朴素贝叶斯算法来解决垃圾邮件分类问题&#xff0c;并使用混淆矩阵进行了验证&#xff0c;得到了非常好的准确率和召回率&#xff08;96%和97%&#xff09;。此外还开发了一个可视化的垃圾邮件分类系统…

前端通过ajax上传文件到七牛云

1. 从服务端获取七牛云上传的token,生成token参考官方文档https://developer.qiniu.com/kodo/1208/upload-token 2. 在七牛云文档查找上传的存储区域 https://developer.qiniu.com/kodo/1671/region-endpoint-fq 在七牛云控制台找到空间管理的cdn加速域名https://portal.qiniu…

【Thinkphp 6】框架基础知识

文章目录 环境搭建框架基础规则继承引入单应用模式多应用模式自定义路由调试器空控制器 视图模板引擎安装渲染模板facade代理变量传递view.php语法查看编译后的文件默认值数组按键取值md5加密 请求request信息参数接收生成URL 文件上传上传及验证 验证功能验证器表单令牌 中间件…

android不可不知调试技巧

目录 1、条件断点 2、评估表达式&#xff08;Evaluate Expression&#xff09; 3、日志断点 4、方法断点 5、异常断点 6、Field WatchPoint 1、条件断点 假设我们列表循环的某个元素时候才暂停&#xff0c;就用这种方式。具体方式在循环列表打断点&#xff0c;对着断点右…

Nginx简介和快速入门

前言: 在一个小型的个人博客网站中&#xff0c;因为没什么流量&#xff0c;并发量小&#xff0c;一般可以直接在一个服务器上的tomcat中直接运行jar包.由tomcat直接响应给客户。 到后面之后随着流量的增大&#xff0c;一台服务器的资源不够用了&#xff0c;此时就需要再多开一…

浏览器渲染页面的原理及流程

1、渲染引擎首先通过网络获得所请求文档的内容 2、解析HTML文件&#xff0c;构建 DOM Tree 3、解析CSS&#xff0c;构建 CSSOM Tree(CSS规则树) 4、将 DOM Tree 和 CSSOM Tree合并&#xff0c;构建Render tree(渲染树) 5、reflow(重排、回流)&#xff1a;根据Render tree进行节…