Java多线程之线程池

news2025/1/17 22:11:17

Java高并发应用开发过程中会频繁的创建和销毁线程,为了节约成本和提升性能,往往会使用线程池来统一管理线程,使用线程池主要有以下几点优势
降低资源消耗:重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度:任务可以不需要等待线程创建就能立即执行
提高线程可管理性:线程池会保持一些基本的线程统计信息,以便对线程进行有效管理,使得能对所接收到的异步任务进行高效调度

1. 使用Executors创建线程

Executors工厂类提供了四种快捷方式创建线程池

方法名称说明
newSingleThreadExecutor()创建只有一个线程的线程池
newFixedThreadPool(int nThreads)创建固定大小的线程池
newCachedThreadPool创建一个不限制线程数量的线程池,提交的任务都将立即执行,但空闲线程会得到及时回收
newScheduledThreadPool创建一个可定期或延时执行任务的线程池

1.1. newSingleThreadExecutor

只有一个线程的线程池,用唯一的工作线程来执行任务,可以保证所有任务按照指定顺序执行
该线程池特点如下:

  1. 单线程化的线程池中的任务是按照提交的次序顺序执行
  2. 池中的唯一线程的存活时间是无限的
  3. 当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的

使用场景:任务按照提交次序,一个任务一个任务逐个执行

1.2. newFixedThreadPool

用于创建固定数量的线程池,唯一的参数用于设置池中线程的固定数量
该线程池特点如下:

  1. 线程池没有达到固定数量时,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定数量
  2. 线程池的大小一旦达到固定数量会保持不变,如果某个线程因执行异常而结束,那么线程池会补充一个新线程
  3. 在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列

使用场景:需要任务长期执行,适用于处理CPU密集型任务

1.3. newCachedThreadPool

创建一个可缓存线程池,如线程池内某些线程无事可做成为空闲线程,可缓存线程池可灵活回收这些空闲线程
该线程池特点如下:

  1. 在接收新的异步任务target执行目标实例时,如果池内所有线程繁忙,此线程池就会添加新线程来处理任务
  2. 此线程池不会对线程池大小进行限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
  3. 如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,就会回收空闲(60秒不执行任务)线程

使用场景:快速处理突发性强、耗时较短

1.4. newScheduledThreadPool

创建一个可调度线程池,提供延时和周期性任务调度功能
当被调任务的执行时间大于指定的间隔时间时,并不会创建一个新的线程去并发执行这个任务,而是等待前一次调度执行完毕
使用场景:周期性执行任务

1.5. Executors创建线程池存在的问题

FixedThreadPool和SingleThreadPool
创建的线程池工作队列的长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致OOM
CachedThreadPool和ScheduledThreadPool
创建的线程池允许创建的线程数量为Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM

2. 使用ThreadPoolExecutor创建线程池

在实际开发过程中创建线程池都是使用构造器ThreadPoolExecutor去构造工作线程池,源码如下:

public ThreadPoolExecutor(
		int corePoolSize,//核心线程数,即使线程空闲也不会回收
		int maximumPoolSize,//最大线程数
		long keepAliveTime, TimeUnit unit,//线程最大空闲时长
		BlockingQueue<Runnable> workQueue,//工作队列
		ThreadFactory threadFactory,//新线程产生方式(线程工厂)
		RejectedExecutionHandler handler)//拒绝策略

2.1. 核心和最大线程数

线程池执行器将会根据corePoolSize和maximumPoolSize自动维护线程池中的工作线程
当在线程池接收到新任务,并且当前工作线程数少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理请求,直到线程数达到corePoolSize
如果当前工作线程数多于corePoolSize数,但小于maximumPoolSize数,那么仅当任务队列已满时才会创建新线程,设置corePoolSize和maximumPoolSize相同,可以创建一个固定大小的线程池
当maximumPoolSize被设置为无界值(Integer.MAX_VALUE)时,线程池可以接收任意数的并发任务
corePoolSize和maximumPoolSize不仅能在线程池构造时设置,也可以调用setCorePoolSize()和setMaximumPoolSize()两个方法进行动态更改

2.2. BlockingQueue

实例用于暂时接收到的异步任务,当线程池核心线程都不可用,那么接收到的目标任务会缓存在该队列中

2.3. keepAliveTime

用于设置池内线程最大空闲时长,超过该时长非核心线程会被回收。如果池在使用过程中提交任务频率变高,也可以调用方法setKeepAliveTime(long, TimeUnit)进行线程存活时长的动态变更,如需防止空闲线程被终止,可以将空闲时长设置为无限大。默认情况下,空闲超时策略仅适用于存在超时corePoolSize线程的情况,若调用了allowCoreThreadTimeOut(boolean)方法,且传入了参数true,则keepAliveTime参数所设置的Idle超时策略也将被应用于核心线程

2.4. 向线程池提交任务的两种方式

调用execute()方法

void execute(Runnable command);

调用submit()方法

Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);

两个方法的区别如下:

  1. 接收的参数不一样
    execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable、Runnable两种类型的参数。Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果
    Runnable和Callable的主要区别为:Callable允许有返回值,Runnable不允许有返回值;Runnable不允许抛出异常,Callable允许抛出异常
  2. submit()提交任务后会有返回值,而execute()没有
    execute()方法主要用于启动任务的执行,submit()方法也用于启动任务的执行,启动之后会返回Future对象,表示一个异步执行实例,通过该异步执行实例去获取结果
  3. submit()方便Exception处理
    execute()方法在启动任务执行后,不关心任务执行过程中可能发生的异常。而通过submit()方法返回的Future对象(异步执行实例),可以进行异步执行过程中的异常捕获

2.5. 线程池任务调度流程

  1. 当前工作线程数小于核心线程数,优先创建任务线程,而不是从线程队列中获取一个空闲线程
  2. 当池中任务数大于核心线程池数,新任务加入阻塞队列。核心线程数用完、阻塞队列没满时线程池不会创建新线程
  3. 当完成任务时,优先从阻塞队列获取下一个任务,直到阻塞队列为空,所有缓存任务执行完成
  4. 当核心线程数用完,阻塞队列已满,线程池接收到新任务,会创建一个非核心线程,并立即执行新任务
  5. 当核心线程数用完,阻塞队列已满,线程总数超过maximumPoolSize,线程池会拒绝接收新任务,执行拒绝策略

线程池调度执行流程

2.6. ThreadFactory

ThreadFactory线程工厂只要一个方法

Thread newThread(Runnable r);

创建新线程时,可以更改所创建的新线程的名称、线程组、优先级、守护进程状态。使用Executors创建新的线程池时,也可以基于ThreadFactory创建,只需要指定ThreadFactory实例,如果没有指定会使用Executors.defaultThreadFactory默认实例,这样所创建的线程位于同一个ThreadGroup中,具有相同的NORM_PRIORITY(优先级为5),且都为非守护进程状态

2.7. 阻塞队列

阻塞队列为空时会阻塞当前线程的元素获取操作,在一个线程从一个空的阻塞队列中获取元素时线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒。
BlockingQueue是JUC包中的一个接口,常用的实现类有以下几种:
ArrayBlockingQueue:数组实现的有界阻塞队列,队列中的元素按FIFO排序,创建时必须设置大小,线程池任务超过corePoolSize时,任务缓存的数量只能为创建时设置的大小,若该阻塞队列已满,则会为新的任务创建线程,直到线程池中的线程总数大于maximumPoolSize
LinkedBlockingQueue:基于链表实现的阻塞队列,按FIFO排序任务,可以设置容量,默认为Integer.Max_VALUE,当接收的任务数超过corePoolSize时,新任务无限地缓存进该队列,直到资源耗尽,快捷创建线程池有两个方法Executors.newSingleThreadExecutor和Executors.newFixedThreadPool使用了这个队列,队列吞吐量高于ArrayBlockingQueue
PriorityBlockingQueue:具有优先级的无界队列
DelayQueue无界阻塞延迟队列,基于PriorityBlockingQueue实现,队列中每个元素都有过期时间,当从队列获取元素时,只有已经过期的元素才会出队,队列头部的元素是过期最快的元素,Executors.newScheduledThreadPool所创建的线程池使用此队列
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程的调用移除操作,否则插入操作一直处于阻塞状态,吞吐量高于LinkedBlockingQueue,Executors.newCachedThreadPool所创建的线程池使用此队列,不会保存提交的任务,而是直接新建一个线程来执行新来的任务

2.8. 拒绝策略

线程池的缓存队列为有界队列时,如果队列满了,提交新任务到线程池时会被拒绝,任务被拒绝通常有两种情况

  1. 线程池已经被关闭
  2. 工作队列已满且maximumPoolSize已满

常见的拒绝策略有如下几种:
AbortPolicy:拒绝策略
如果线程池队列满了,新任务就会被拒绝,且抛出RejectedExecutionException异常,线程池默认拒绝策略
DiscardPolicy:抛弃策略
如果线程池队列满了,新任务就会直接被丢弃,且不会抛出异常
DiscardOldestPolicy:抛弃最早任务策略
如果线程池队列满了,将最早进入队列(队列头)的任务抛弃,从队列中腾出空间,再尝试加入队列
CallerRunsPolicy:调用者执行策略
在新任务被添加进线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务
自定义策略
也可以自定义一个拒绝策略,只需要实现RejectedExecutionHandler接口的rejectedExecution方法即可

2.9. 线程池的五种状态

RUNNING:线程池创建之后的初始状态,此状态下可以执行任务
SHUTDOWN:该状态线程池不接受新任务,会将工作队列中的任务执行完毕
STOP:该状态线程池不接受新任务,也不处理工作队列中剩余任务,且将会中断所有工作线程
TIDYING:该状态所有任务都已终止或处理完成,将会执行terminated()钩子方法
TERMINATED:执行完terminated()钩子方法之后的状态
线程池状态转换规则

  1. 线程池创建之后状态为RUNNING
  2. 执行shutdown()实例方法,线程池状态从RUNNING转变为SHUTDOWN
  3. 执行shutdownNow()实例方法,线程池状态从RUNNING转变为STOP
  4. 线程池状态为SHUTDOWN时,执行hutdownNow()方法会将其状态转变为STOP
  5. 线程池所有工作线程停止,工作队列清空后,状态从STOP转为TIDYING
  6. 执行完terminated()钩子方法之后,线程池状态从TIDYING转变为TERMINATED

关闭线程池步骤如下:

  1. 执行shutdown()方法,拒绝新任务的提交,并等待所有任务有序执行完毕
  2. 执行awaitTermination(long timeout,TimeUnit unit)方法,指定超时时间,判断是否已经关闭所有任务,线程池关闭完成
  3. 如果awaitTermination()方法返回false,或被中断,就调用shutDownNow()方法立即关闭线程池所有任务
  4. 补充执行awaitTermination(long timeout,TimeUnit unit)方法,判断线程池是否关闭完成,如果超时,可以进入循环关闭,循环一定的次数,不断关闭线程池,直到其关闭或循环结束

2.10. 确定线程池线程数

按照任务类型对线程池可以分为三类
IO密集型任务
主要是执行IO操作,由于执行IO操作时间较长,导致CPU利用率不高,CPU常处于空闲状态,Netty的IO读写操作为此类任务
核心线程池数 = CPU核数 * 2
CPU密集型任务
主要执行计算任务,由于响应时间快,CPU一直在运行,CPU利用率高
核心线程数 = CPU核心数 + 1
多出一个线程是为了防止线程偶发的缺页中断,或其他原因导致的任务暂停而带来的影响
混合型任务
既要执行逻辑计算,又要进行IO操作,由于执行IO操作耗时较长,CPU利用率也不高,Web服务器的HTTP请求处理操作为此类任务
核心线程数 = (线程等待时长与线程CPU时长之比 + 1) * CPU核数

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

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

相关文章

ImageNet classification with deep convolutional neural networks

使用深度卷积神经网络进行ImageNet图像分类 目录 1.引言 2.网络结构 2.1 小细节 2.2 代码部分 3. 创新点 3.1 非线性激活函数ReLU&#xff08;提速&#xff09; 3.2 多GPU训练&#xff08;提速&#xff09; 3.3局部响应归一化&#xff08;增强泛化能力&#xff0c;已不…

我国天宫空间站以及各个仓位介绍

一、天宫空间站 天宫空间站&#xff08;China Space Station&#xff09;是中国从2021年开始建设的一个模块化空间站系统&#xff0c;为人类自1986年的和平号空间站及1998年的国际空间站后所建造的第三座大型在轨空间实验平台&#xff0c;基本构型由天和核心舱、问天实验舱和梦…

Head First设计模式(阅读笔记)-07.适配器模式

火鸡冒充鸭子 现在缺少一个绿头鸭对象&#xff0c;需要用野生火鸡对象冒充一下&#xff0c;但是二者的接口都不一样该怎么去冒充呢&#xff1f; // 鸭子接口 public interface Duck{public void quack(); // 呱呱叫public void fly(); // 飞行 } // 火鸡接口 public interfac…

应力奇异,你是一个神奇的应力!

在用ANSYS进行压力容器应力分析计算的时候&#xff0c;总会出现一些应力集中的问题&#xff0c;而且&#xff0c;有些应力集中点竟然没办法采用倒圆角的办法消除&#xff0c;采用网格加密方法时&#xff0c;甚至应力值比之前更大。这个情况&#xff0c;大家通常称为应力奇异。 …

springboot-mybatisplus-redis二级缓存

前言 mybatis可以自己带有二级缓存的实现&#xff0c;这里加上redis是想把东西缓存到redis中&#xff0c;而不是mybaits自带的map中。这也就构成了我们看到的springboot mybatisplus redis实现二级缓存的题目。 具体步骤如下&#xff1a; 首先加入需要的依赖 <dependenc…

《InnoDB引擎六》InnoDB 1.0.x版本之前的Master Thread

Master Thread 工作方式 在后台线程中提到&#xff0c;Master Thread是核心的后台线程。InnoDB存储引擎的主要工作都是在一个单独线程中完成的。 InnoDB 1.0.x版本之前的Master Thread Master Thread具有最高的线程优先级别。内部由多个循环组成&#xff1a;主循环(loop)、后台…

[附源码]SSM计算机毕业设计医院仪器设备管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于萤火虫算法优化的BP神经网络预测模型(Matlab代码实现)

目录 1 概述 2 萤火虫算法 3 萤火虫算法优化BP神经网络的算法设计 3.1 基本思想 3.2 萤火虫算法优化BP神经网络算法 4 运行结果 5 参考文献 6 Matlab代码及文章 1 概述 现实的世界中混沌现象无处不在,大至宇宙,小到基本粒子,都受到混沌理论支配.如气候变化会出 现混沌…

(三)DepthAI-python相关接口:OAK Nodes

消息快播&#xff1a;OpenCV众筹了一款ROS2机器人rae&#xff0c;开源、功能强、上手简单。来瞅瞅~ 编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查…

[MySQL]-压力测试_TPCC-MySQL

[MySQL]-压力测试_TPCC-MySQL 森格 | 2022年10月 对数据库学习来说&#xff0c;压力测试也是十分必要的一环&#xff0c;本文章主要介绍了TPCC-MySQL这个工具的使用。 一、基本概念 1.1 基准测试VS压力测试 基准测试&#xff1a; 直接简单、易于比较&#xff0c;用于评估服务…

CAD必练图形

这次我们用CAD梦想画图软件绘制一个CAD新手必练图形&#xff0c;它用到的有CAD矩形、直线、圆弧、等分等命令结合起来完成绘制的&#xff0c;可以跟着一起操作一下。 目标图形 操作步骤 1.使用CAD矩形命令&#xff08;快捷键&#xff1a;REC&#xff09;绘制一个长80宽30的矩…

为什么macbook不能删除u盘里东西?苹果电脑如何删除u盘文件

为什么macbook不能删除u盘里东西&#xff1f;有时候&#xff0c;我们会发现U盘不能够在Mac上正常使用&#xff0c;只能够读取U盘上的文件数据&#xff0c;但是若想要对其进行删除或者是编辑操作&#xff0c;完全不能够实现&#xff0c;本文为大家详细介绍了不同情况的不同解决方…

云原生主题学习月|共同学习全球领先的亚马逊云科技云原生课程,组团共学拿奖励~

CSDN 已上线亚马逊云科技超过 60 门中文数字化培训课程&#xff0c;希望为学习者提供亚马逊云科技基础技能知识和最佳实践。 每门课程时长从十分钟到几小时不等&#xff0c;由亚马逊云科技专家打造&#xff0c;其中包括最受欢迎的《亚马逊云科技云从业者必修知识》&#xff0c…

vue3发送验证码倒计时 (防止连点、封装复用)

一、实现思路 倒计时 流程图二、实现一个简单的验证码倒计时 //倒计时初始变量 const codeNum ref(60); // 定时器id let clearId: number; // 发送验证码 const sendCode async () > { // 防止下次点击 如果倒计时的时间不是60 就不执行下面逻辑if (codeNum.value ! 60)…

AcWing算法学习第三节---高精度问题.

系列文章目录 第一节快速排序 第二节二分法 学习路上的风景&#xff0c;我陪你一起去看&#xff0c;编程路上的算法&#xff0c;我陪你一起去学&#xff0c;朋友们你们好&#xff0c;我是夏目浅石&#xff0c;蟹蟹你点开文章和我一同进步&#xff0c;加油&#xff01;遇见更好…

2022Q3手机配件增长榜:手机壳、数据线等供求不断增加

本篇我们将继续来分析22年Q3季度中手机通讯行业的高增长概念。在手机通讯行业中&#xff0c;我们发现了3个高增长品类&#xff0c;分别是&#xff1a;手机耳机、手机壳、数据线。 一、手机配件类高增长概念——手机耳机 手机耳机可以分为不同种类&#xff0c;如&#xff1a;开放…

现代物流有哪些特点?

现代物流的特点有系统化、网络化、信息化、专用化和准时性和柔性化。 现代物流特点一&#xff1a;系统化 现代物流强调了物流体系是一个具有综合性和系统性的过程&#xff0c;以优化物流管理体系为目标&#xff0c;降低物流运营管理成本&#xff0c;实现企业效益提升&#xff0…

python笔记76-types.FunctionType 动态创建函数

前言 types.FunctionType 创建函数有2种方式&#xff1a; 从已有函数的基础上&#xff0c;创建一个新函数从一个compile 构建的函数对象上&#xff0c;创建一个新函数 FunctionType 使用 FunctionType 可以用于判断一个对象是不是函数 from types import FunctionType, Me…

中国互联网综合实力100强:猿辅导第39

11月2日&#xff0c;中国互联网企业综合实力指数发布会暨百家企业高峰论坛在厦门成功举办。发布会上&#xff0c;中国互联网协会正式发布了《中国互联网企业综合实力指数&#xff08;2022&#xff09;》以及2022年中国互联网综合实力前百家企业榜单。 北京猿力教育科技有限公司…

腾讯基础面

传送门1. 有了解过C吗&#xff1f;接受转语言吗&#xff1f;2. 有没有了解过一些框架的底层原理、底层优化、数据库的索引优化3. 了解过哪些Map&#xff0c;可以从底层简单说下嘛&#xff1f;4. 你项目中是如何去实现幂等性的&#xff1f;5. RPC的协议讲一讲&#xff0c;怎么处…