《深入理解 Java 线程池:高效管理线程的利器》

news2024/9/19 9:53:49

线程池

1. 什么是线程池?

​ 线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,线程池会创建一个新线程进行处理或者放入队列(工作队列)中等待。

2.线程池常用类和接口

​ 在Java标准库提供了如下几个类或接口,来操作并使用线程池:

1.ExecutorService接口:进行线程池的操作访问;

2.Executors类:创建线程池的工具类;

3.ThreadPoolExecutor及其子类:封装线程池的核心参数和运行机制;

线程池的基本使用方式:

// 线程池基本使用方式
// 创建一个ThreadPoolExecutor类型的对象,代表固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3); // 该线程池拥有3个线程
// 执行线程任务
executor.execute(task1);
executor.execute(task2);
executor.execute(task3);
executor.execute(task4);
executor.execute(task5);
// 使用结束后,使用shutdown关闭线程池
executor.shutdown();
3.线程池常见方法

●执行无返回值的线程任务:void execute(Runnable command);

●提交有返回值的线程任务:Future submit(Callable task);

●关闭线程池:void shutdown(); 或 shutdownNow();

●等待线程池关闭:boolean awaitTermination(long timeout, TimeUnit unit);

3.1 执行线程任务

​ execute()只能提交Runnable类型的任务,没有返回值,而submit()既能提交Runnable类型任务也能提交Callable类型任务,可以返

回Future类型结果,用于获取线程任务执行结果。

execute()方法提交的任务异常是直接抛出的,而submit()方法是捕获异常,当调用Future的get()方法获取返回值时,才会抛出异常。

// 计算1-100w的之间所有数字的累加和,每10w个数字交给1个线程处理
// 创建一个固定大小的线程池:
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 创建集合,用于保存Future执行结果
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
// 每10w个数字,封装成一个Callable线程任务,并提交给线程池
for (int i = 0; i <= 900000; i += 100000) {
   Future<Integer> result = executorService.submit(new CalcTask(i+1, i + 100000));
  futureList.add(result);
}
// 处理线程任务执行结果
try {
   int result = 0;
   for (Future<Integer> f : futureList) {
       result += f.get();
   }
  System.out.println("最终计算结果" + result);
} catch (InterruptedException e) {
   e.printStackTrace();
} catch (ExecutionException e) {
  e.printStackTrace();

}
// 关闭线程池
// 省略.....
运行结果:最终计算结果:1784293664
3.2关闭线程池

​ 线程池在程序结束的时候要关闭。使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。

shutdownNow()会立刻停止正在执行的任务;

​ 当使用awaitTermination()方法时,主线程会处于一种等待的状态,按照指定的timeout检查线程池。

​ 第一个参数指定的是时间,第二个参数指定的是时间单位(当前是秒)。返回值类型为boolean型。

​ 如果等待的时间超过指定的时间,但是线程池中的线程运行完毕,awaitTermination()返回true。

​ 如果等待的时间超过指定的时间,但是线程池中的线程未运行完毕,awaitTermination()返回false。

​ 如果等待时间没有超过指定时间,则继续等待。

该方法经常与shutdown()方法配合使用,用于检测线程池中的任务是否已经执行完毕:

// 线程池已提交或执行若干个任务
// 关闭线程池:必须等待任务执行结束后,线程池才会关闭
executorService.shutdown();
// 每隔1秒钟,检查一次线程池的任务执行状态
while(!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
   System.out.println("还没有关闭!");
}
System.out.println("已关闭!");
4.线程池的执行流程重要

●1. 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;

●2. 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。

​ ○如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务;

​ ○如果大于核心线程数corePoolSize,线程池会检查工作队列;

​ ■如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;

​ ■如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;

​ ●如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务;

​ ●如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务;

综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略。

image.png

5.线程池的配置参数重要

●corePoolSize线程池核心线程数:也可以理解为线程池维护的最小线程数量,核心线程创建后不会被回收。大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收;

​ ○在创建了线程池后,默认情况下,线程池中并没有任何线程,当调用 execute() 方法添加一个任务时,如果正在运行的线程数量小于corePoolSize,则马上创建新线程并运行这个任务。

​ ○IO密集计算:由于 I/O 设备的速度相对于 CPU来说都很慢,所以大部分情况下,I/O 操作执行的时间相对于 CPU 计算来说都非常长,这种场景我们一般都称为 I/O 密集型计算。最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]。

​ ○CPU密集型:CPU 密集型计算大部分场景下都是纯 CPU 计算,多线程主要目的是提升CPU利用率,最佳线程数 =“CPU 核心数 +1”。这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以临时替补,从而保证 CPU 的利用率。

●maximumPoolSize线程池最大线程数:线程池允许创建的最大线程数量;(包含核心线程池数量)

●keepAliveTime非核心线程线程存活时间:当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。

​ ○当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被回收,直到线程池中的线程数不超过corePoolSize。

​ ○如果设置allowCoreThreadTimeOut = true,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

●TimeUnit时间单位:参数keepAliveTime的时间单位;

●BlockingQueue阻塞工作队列:用来存储等待执行的任务;

●ThreadFactory线程工厂 : 用于创建线程,以及自定义线程名称,需要实现ThreadFactory接口;

●RejectedExecutionHandler拒绝策略:当线程池线程内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理;

​ ○ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常;

​ ○ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常;

​ ○ThreadPoolExecutor.DiscardOldestPolicy:丢弃工作队列中的队头任务(即最旧的任务,也就是最早进入队列的任务)后,继续将当前任务提交给线程池;

​ ○ThreadPoolExecutor.CallerRunsPolicy:由原调用线程处理该任务 (谁调用,谁处理);

6.线程池分类

​ Java标准库提供的几种常用线程池,创建这些线程池的方法都被封装到Executors工具类中。

●FixedThreadPool:线程数固定的线程池,使用Executors.newFixedThreadPool()创建;

●CachedThreadPool:线程数根据任务动态调整的线程池,使用Executors.newCachedThreadPool()创建;

●SingleThreadExecutor:仅提供一个单线程的线程池,使用Executors.newSingleThreadExecutor()创建;

●ScheduledThreadPool:能实现定时、周期性任务的线程池,使用Executors.newScheduledThreadPool()创建;

6.1 FixedThreadPool线程池

线程数固定的线程池

以FixedThreadPool线程池为例,线程池的执行步骤如下:

public class Main {
public static void main(String[] args) {
       // 创建一个固定大小的线程池:
      ExecutorService executorService = Executors.newFixedThreadPool(4);
      for (int i = 0; i < 6; i++) {
       	executorService.execute(new Task("线程"+i));
      }
      // 关闭线程池:
       executorService.shutdown();
  }
}
class Task implements Runnable {
	private String taskName;
	public Task(String taskName) {
	this.taskName = taskName;
	}
   @Override
   public void run() {
        System.out.println("启动线程 ===> " + this.taskName);
      try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
     }
        System.out.println("结束线程 <= " + this.taskName);
   }
}

执行分析:

●观察执行结果,一次性放入6个任务,由于线程池只有固定的4个线程,因此,前4个任务会同时执行,等到有线程空闲后,才会执行后面的两个任务。

6.2 CachedThreadPool线程池

线程数根据任务动态调整的线程池

●把线程池改为CachedThreadPool,观察运行结果:

ExecutorService executorService = Executors.newCachedThreadPool();

执行分析:

●观察执行结果,由于这个线程池的实现会根据任务数量动态调整线程池的大小,所以6个任务可一次性全部同时执行。

6.3 ScheduledThreadPool线程池

能实现定时、周期性任务的线程池

●例如:每秒刷新证券价格。这种任务本身固定,需要反复执行的,可以使用ScheduledThreadPool;

●放入ScheduledThreadPool的任务可以定期反复执行;

创建ScheduledThreadPool定时任务线程池

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);

延迟3秒钟后执行,任务只执行1次

executorService.schedule(new Task("线程A"), 3, TimeUnit.SECONDS);

延迟2秒钟后,每隔3秒钟执行任务1次

// 方式1
executorService.scheduleAtFixedRate(new Task("线程A"), 2,3, TimeUnit.SECONDS);
// 方式2
executorService.scheduleWithFixedDelay(new Task("线程A"), 2,3, TimeUnit.SECONDS);

FixedRate和FixedDelay的区别:

●FixedRate是指任务总是以固定时间间隔触发,不管任务执行多长时间;

●FixedDelay是指,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务;

7.线程池的状态

线程池的状态分为:RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED

○ RUNNING:运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。

■调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;

■调用线程池的shutdownNow()方法,可以切换到STOP停止状态;

○SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;

■当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入TIDYING状态;

○STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;

■线程池中执行的任务为空,进入TIDYING状态;

○TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;

■terminated()执行完毕,进入TERMINATED状态;

○TERMINATED: 终止状态,该状态表示线程池彻底关闭。

线程池状态.webp

8.线程池分类总结重要
8.1 FixedThreadPool

线程数固定的线程池

线程池参数:

○核心线程数和最大线程数一致

○非核心线程线程空闲存活时间,即keepAliveTime为0

○阻塞队列为无界队列LinkedBlockingQueue

●工作机制:

a提交线程任务

b如果线程数少于核心线程,创建核心线程执行任务

c如果线程数等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列

d如果线程执行完任务,去阻塞队列取任务,继续执行

●使用场景: 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

8.2 CachedThreadPool

可缓存线程池,线程数根据任务动态调整的线程池

线程池参数:

○核心线程数为0

○最大线程数为Integer.MAX_VALUE

○工作队列是SynchronousQueue同步队列

○非核心线程空闲存活时间为60秒

●工作机制:

a提交线程任务

b因为核心线程数为0,所以任务直接加到SynchronousQueue工作队列

c判断是否有空闲线程,如果有,就去取出任务执行

d如果没有空闲线程,就新建一个线程执行

e执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。

●使用场景: 用于并发执行大量短期的小任务。

8.3 SingleThreadExecutor

单线程化的线程池

●线程池参数:

○核心线程数为1

○最大线程数也为1

○阻塞队列是LinkedBlockingQueue

○非核心线程空闲存活时间为0秒

●使用场景: 适用于串行执行任务的场景,将任务按顺序执行。

8.4 ScheduledThreadPool

能实现定时、周期性任务的线程池

线程池参数:

○最大线程数为Integer.MAX_VALUE

○阻塞队列是DelayedWorkQueue

○keepAliveTime为0

●使用场景: 周期性执行任务,并且需要限制线程数量的需求场景。

9. 线程池使用注意事项

​ 在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创

建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用

Executors去创建,而要通过ThreadPoolExecutor方式。jdk中Executor框架虽然提供了如newFixedThreadPool()、

newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部

也是通过new ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需

要的线程池,避免资源耗尽的风险。

里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创

建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用

Executors去创建,而要通过ThreadPoolExecutor方式。jdk中Executor框架虽然提供了如newFixedThreadPool()、

newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部

也是通过new ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需

要的线程池,避免资源耗尽的风险。

​ 必须为线程池中的线程,按照业务规则,进行命名。可以在创建线程池时,使用自定义线程工厂规范线程命名方式,避免线程使用默认名称。

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

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

相关文章

【sgCreateCallAPIFunctionParam】自定义小工具:敏捷开发→调用接口方法参数生成工具

<template><div :class"$options.name" class"sgDevTool"><sgHead /><div class"sg-container"><div class"sg-start"><div style"margin-bottom: 10px">参数列表[逗号模式]<el-too…

小微金融企业系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;贷款信息管理&#xff0c;贷款申请管理&#xff0c;贷款种类管理&#xff0c;代办项目管理&#xff0c;项目分类管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;代办项…

基于51单片机的物联网安防系统(Proteus+Python脚本+阿里云)

基于51单片机的物联网安防系统使用Proteus进行仿真&#xff0c;LCD12864进行数据显示&#xff0c;集成了温湿度传感器、烟雾、甲烷传感器&#xff0c;执行器件是风扇&#xff0c;采用L298进行驱动&#xff0c;按键实现用户交互&#xff0c;蜂鸣器报警&#xff0c;红外检测人员状…

如何将示波器输出的电压数据转换为频域数据,五句代码解决问题,详细分析五句代码

这四句代码是关于对电压数据&#xff08;voltage&#xff09;进行快速傅里叶变换&#xff08;FFT&#xff09;&#xff0c;并生成相应的频率向量&#xff0c;提取并计算频谱的幅度&#xff0c;同时只保留正频率部分。 五句代码如下图所示&#xff1a; % 计算采样频率dt mean(…

Python | Leetcode Python题解之第413题等差数列划分

题目&#xff1a; 题解&#xff1a; class Solution:def numberOfArithmeticSlices(self, nums: List[int]) -> int:n len(nums)if n 1:return 0d, t nums[0] - nums[1], 0ans 0# 因为等差数列的长度至少为 3&#xff0c;所以可以从 i2 开始枚举for i in range(2, n):i…

Rust GUI框架 tauri V2 项目创建

文章目录 Tauri 2.0创建应用文档移动应用开发 Android 前置要求移动应用开发 iOS 前置要求参考资料 Tauri 2.0 Tauri 是一个构建适用于所有主流桌面和移动平台的轻快二进制文件的框架。开发者们可以集成任何用于创建用户界面的可以被编译成 HTML、JavaScript 和 CSS 的前端框架…

电源电压输入输出保护电路

输入正负极接反芯片损坏 解决方案&#xff1a;添加防反接电路(蓝色虚线框中电路)。 Q1:VDS≥1.5*VINMAX&#xff1b; DZ1:VDZ110V&#xff0c;500mW&#xff1b; R3:20K&#xff1b; R4:20K。 输入尖峰电压损坏芯片 ➢ 解决方案一&#xff1a;输入添加瞬态尖峰电压吸收电路…

【中国留学网-注册_登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

如何兼容性地开发响应式站点——WEB开发系列40

CSS在不同浏览器的支持历史中&#xff0c;有过多次变革。尽管现代浏览器逐步趋向一致&#xff0c;但仍有一些较旧的浏览器广泛使用&#xff0c;特别是在某些地区或特定环境中。 一、浏览器生态&#xff1a;了解你的网站用户 在设计和开发站点之前&#xff0c;了解目标用户所使…

多旅行商问题:鹈鹕优化算法(Pelican Optimization Algorithm,POA)求解多仓库多旅行商问题MD-MTSP(提供Matlab代码)

一、鹈鹕优化算法 鹈鹕优化算法(Pelican Optimization Algorithm,POA)由Pavel Trojovsk和Mohammad Dehghani 于2022年提出&#xff0c;该算法模拟了鹈鹕在狩猎过程中的自然行为。 鹈鹕很大&#xff0c;喙很长&#xff0c;喉咙里有一个大袋子&#xff0c;用来捕捉和吞咽猎物。…

记录一下ElementUI 3 在浏览器导入, table表格显示问题

当时问题忘了截图, 现在通过文字记录一下问题 我直接在html了引入 vue3 和 ElementUI 3 , 使用了table组件, 但是表格的td 总是只显示一列, 问题是我的 el-table-column 标签 没有结束标签 , 在vue文件模块化里写不需要结束标签, 在浏览器里无法直接识别出来, 所以他是渲染了第…

鸿蒙开发之ArkUI 界面篇 十 边框border

border语法格式如下&#xff1a; 要实现如下效果&#xff1a; 代码如下&#xff1a; Entry Component struct IndexTest {State message: string IndexTest;build() {Column(){Text(border实现).fontSize(30) .border({width:4,color:Color.Red,style:BorderStyle.Solid,ra…

进程的重要函数

进程的重要函数: fork函数 了解fork函数 通过调用fork()函数&#xff0c;则会产生一个新的进程。调用fork()函数的进程叫做 父进程&#xff0c;产生的新进程则为子进程。 其编码过程: 1.函数功能: 函数头文件 #include <sys/types.h> #include <unistd.h> 函数…

【FFT】信号处理——快速傅里叶变换【通俗易懂】

快速傅里叶变换&#xff08;Fast Fourier Transform, FFT&#xff09;是一种用于将信号从时间域转换到频率域的算法。 傅里叶变换的核心思想是&#xff1a;任何周期性信号都可以分解成多个不同频率的正弦波或余弦波的叠加。 简单来说&#xff0c;FFT可以帮助我们理解一个信号…

使用 Internet 共享 (ICS) 方式分配ip

设备A使用dhcp的情况下&#xff0c;通过设备B分配ip并共享网络的方法。 启用网络共享&#xff08;ICS&#xff09;并配置 NAT Windows 自带的 Internet Connection Sharing (ICS) 功能可以简化 NAT 设置&#xff0c;允许共享一个网络连接给其他设备。 打开网络设置&#xff1…

力扣之1075.项目员工I

文章目录 1. 1075.项目员工I1.1 题干1.2 准备数据1.3 解法1.4 结果截图 1. 1075.项目员工I 1.1 题干 项目表 Project&#xff1a; -------------------- | Column Name | Type | -------------------- | project_id | int | | employee_id | int | -------------------- 主键…

『小白可入』VSPD虚拟串口工具——从此告别硬件串口调试

一、下载VSPD工具 工具已上传至百度云&#xff0c;在以下地址下载&#xff1a; VSPD下载链接&#xff1a;通过百度网盘分享的文件&#xff1a;我的资源 链接&#xff1a;https://pan.baidu.com/s/1x2eoQYg6erqs__CQgT5j6Q?pwd4211 提取码&#xff1a;4211 二、安装 1.解压后的…

2024年Apple Search Ads(简称:苹果ASA):开展有效活动的秘诀

当谈到为应用程序启动广告活动时&#xff0c;许多人会立即想到Android。然而&#xff0c;这并不总是最好的选择&#xff0c;因为iOS设备在多个国家和地区占据市场主导地位。在这些地区&#xff0c;定位ios用户可以带来更大的成功。 您可以通过各种渠道在iOS上投放广告&#xff…

最低成本的游戏串流方案分享 如何自己打造云电脑?

今天教大家如何最低成本实现串流 出门在外也可以随时随地游玩端游大作 硬件准备&#xff1a;一台电脑 手机/平板一台 软件&#xff1a;Gameviewer远程 为啥不用moonlight等其他软件呢 因为设置公网穿透等复杂操作对小白来说不太友好 而GameViewer从安装到使用仅需一键 对比同类…

PostgreSQL技术内幕10:PostgreSQL事务原理解析-日志模块介绍

文章目录 0.简介1.PG日志介绍2.事务日志介绍3.WAL分析3.1 WAL概述3.2 WAL设计考虑3.2.1 存储格式3.2.2 实现方式3.2.3 数据完整性校验3.3 check ponit 4.事务提交日志&#xff08;CLOG&#xff09;4.1 clog存储使用介绍4.2 slru缓冲池并发控制 0.简介 本文将延续上一篇文章内容…