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

线程池

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;}@Overridepublic 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.xdnf.cn/news/143978.html

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

相关文章

不得不说 Sam‘s Club 的数字化做得挺好

因正好有东西要退货就顺便看了下订单如何退货。 但发现 Sam’s Club 的所有交易都能够从后台查到&#xff0c;同时还提供了个 CSV 文件的下载。 打开下载文件就能看到全部的数字化的交易记录。 就拿加油这个事情来说&#xff0c;能够非常清楚这一年在 Sam’s Club 加油多少加…

【无人机设计与控制】基于粒子群算法的三维无人机航迹规划

摘要 本文研究了基于粒子群算法&#xff08;PSO&#xff09;的三维无人机航迹规划问题。通过粒子群优化算法&#xff0c;无人机能够在复杂三维环境中进行路径规划&#xff0c;以避开障碍并实现最优路径飞行。该方法有效结合了无人机的飞行动力学特性与环境约束&#xff0c;能够…

【GlobalMapper精品教程】087:基于DEM的山脊线提取

文章目录 一、山脊线介绍二、加载实验数据三、山脊线提取一、山脊线介绍 山脊线是指沿山脊走向布设的路线,通常也是山脊最高点的连线,它在地形表示和测绘科学技术中具有重要地位。 山脊线是大体上沿分水岭布设的路线,也被称为山脊的最高棱线。在等高线地图上,山脊线表现为…

计算机网络(七) —— https协议与网络安全证书

目录 一&#xff0c;关于https 二&#xff0c;关于加密 2.1 明文&#xff0c;密钥 2.2 对称和非对称加密 2.3 数据摘要&#xff0c;数据指纹&#xff0c;数字签名 三&#xff0c;https过程过程探究 四&#xff0c;证书 4.1 CA认证 4.2 证书大致内容和申请流程 4.3 签…

fastadmin 定义通用搜索默认值,实现datetimerange默认搜索今日的列表数据,列表查询慢解决方案

文章目录 前言实例完结 前言 fastadmin默认会展示全部列表数据&#xff0c;随着数据越来越多&#xff0c;一次性查询出全部的数据会导致列表查询越来越慢 sql优化可以查看我这篇文章&#xff1a;分享最全的sql优化解决方案 除了基本的sql优化查询速度外&#xff0c;我们还可…

网络爬虫到底难在哪里?

如果你是自己做爬虫脚本开发&#xff0c;那确实难&#xff0c;因为你需要掌握Python、HTML、JS、xpath、database等技术&#xff0c;而且还要处理反爬、动态网页、逆向等情况&#xff0c;不然压根不知道怎么去写代码&#xff0c;这些技术和经验储备起码得要个三五年。 比如这几…

Cursor免费 GPT-4 IDE 工具的保姆级使用教程

Cursor免费 GPT-4 IDE 工具的保姆级使用教程 简介 Cursor 是一款基于人工智能技术的代码生成工具。 它利用先进的自然语言处理和深度学习算法&#xff0c;可根据用户的输入或需求&#xff0c;自动生成高质量代码。 不管是初学者&#xff0c;还是资深开发者&#xff0c;Curs…

低代码技术:简化应用开发的未来

近年来&#xff0c;低代码技术作为一种新兴的应用开发方法&#xff0c;受到了广泛关注。低代码平台通过图形化的界面和预设的模块&#xff0c;使得用户能够以较少的代码编写工作创建应用程序。这一技术的发展&#xff0c;标志着软件开发过程中的一个重要变革。 低代码技术的基…

Koa (下一代web框架) 【Node.js进阶】

koa (中文网) 是基于 Node.js 平台的下一代 web 开发框架&#xff0c;致力于成为应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石&#xff1b; 利用 async 函数 丢弃回调函数&#xff0c;并增强错误处理&#xff0c;koa 没有任何预置的中间件&#xff0c;可快速…

二叉树的前中后序遍历(迭代法)( 含leetcode上三道【前中后序】遍历题目)

文章目录 前序遍历&#xff08;迭代法&#xff09;中序遍历&#xff08;迭代法&#xff09;后序遍历&#xff08;迭代法&#xff09;总结 为什么可以用迭代法&#xff08;非递归的方式&#xff09;来实现二叉树的前后中序遍历呢&#xff1f; 在队列与栈专题我们就感受到了&…

KeyCode及KeyCode分发机制

文章目录 需求场景纯KeyCode 快捷操作KeyCode 按键响应操作、拦截 一、工作中常用KeyCode二、KeyCode大全三、KeyCode 响应事件事件输入流程事件响应源码分析源码举例说明 需求场景 纯KeyCode 快捷操作 经常在代码中实现返回、Home 、音量加减、截屏 等功能实现&#xff0c;代…

SpringBoot(40) — SpringBoot整合MyBatis-plus

前言 在上节中我们对MyBatis-plus特性有了一个整体的认识&#xff0c;然后也大致讲了些MyBatis与MyBatis-plus的不同之处。大家感兴趣的话&#xff0c;可参考以下文章 SpringBoot(39) — MyBatis-plus简介 这节我们来讲讲SpringBoot项目如何快速接入MyBatis-plus框架。 今天涉及…

开源网安多城联动、多形式开展网安周公益活动,传播网络安全知识

9月9日至15日&#xff0c;以“网络安全为人民&#xff0c;网络安全靠人民”为主题的2024年国家网络安全宣传周将在全国范围内统一开展&#xff0c;通过多样的形式、丰富的内容&#xff0c;助力全社会网络安全意识和防护技能提升。开源网安今年继续为各地企业、群众带来了丰富的…

BOE(京东方)领先科技赋能体育产业全面向新 以击剑、电竞、健身三大应用场景诠释未来健康运动新生活

巴黎全球体育盛会虽已闭幕&#xff0c;但世界范围内的运动热潮并未消退。9月12日&#xff0c;在北京恒通国际商务园&#xff08;UBP&#xff09;的之所ICC&#xff0c;BOE&#xff08;京东方&#xff09;开启了以“屏实力 FUN肆热爱”为主题的“科技赋能体育”互动体验活动。活…

esp32 wifi 联网后,用http 发送hello 用pc 浏览器查看网页

参考chatgpt Esp32可以配置为http服务器&#xff0c;可以socket编程。为了免除编写针对各种操作系统的app。完全可以用浏览器仿问esp32服务器&#xff0c;获取esp32的各种数据&#xff0c;甚至esp的音频&#xff0c;视频。也可以利用浏览器对esp进行各种操作。但esp不能主动仿…

vue3前端开发-小兔鲜超市-本地购物车列表页面的统计计算

vue3前端开发-小兔鲜超市-本地购物车列表页面的统计计算&#xff01;这一次&#xff0c;实现了一些本地购物车列表页面的&#xff0c;简单的计算。 代码如下所示&#xff1a; import { computed, ref } from vue import { defineStore } from pinia export const useCartStor…

web基础—dvwa靶场(八)SQL Injection(Blind)

SQL Injection(Blind)&#xff08;SQL注入之盲注&#xff09; SQL Injection(Blind)&#xff0c;SQL盲注&#xff0c;相比于常规的SQL注入&#xff0c;他不会将返回具体的数据信息或语法信息&#xff0c;只会将服务器包装后的信息返回到页面中。 常规SQL注入与SQL盲注详细对比…

css 样式简单学习(一)

目录 1. css 介绍 1.1 css 样式 1.2 css代码风格 1.2.1 书写格式 1.2.2 样式大小写​编辑 1.2.3 空格规范 2. 基础选择器 2.1 选择器的作用​编辑 2.2 选择器的分类 2.3 基础选择器 2.3.1 标签选择器​编辑 2.3.2 类选择器​编辑 2.3.3 类选择器-多类名​编辑 2.…

Unity 百度AI实现无绿幕拍照抠像功能(详解版)

目录 一、前言 1.抠像效果 2.去哪找百度ai抠图 3.基础流程跳过 二、获取AccessToken 1.什么是Token 2.为什么要获取Token 3.如何获取token 4.解析json 5.完整代码 三、抠像 1.准备地址 2.建立链接&#xff0c;和基本配置 3.图片格式转换 4.开始上传 5.获取回复…

python本地进程通讯----共享内存变量

背景 最近在开发实践中&#xff0c;接触到了需要多进程开发的场景。众所周知&#xff0c;进程和线程最大的区别就在于&#xff1a;进程是资源分配的最小单位&#xff0c;线程是cpu调度的最小单位。对于多进程开发来说&#xff0c;每一个进程都占据一块独立的虚拟内存空间&#…