四种线程池的创建及任务提交

1. 线程池概述

1.1 线程池的定义

        线程池是管理和控制线程使用的一种手段。它通过提前创建一定数量的线程,并将任务提交给这些线程执行,来实现资源的合理分配和任务的高效处理。

关键点

  • 线程复用:线程池在任务执行完毕后,不会销毁线程,而是让线程继续等待下一个任务。
  • 任务调度:线程池可以通过队列对任务进行排序和分发,确保资源合理利用。
  • 统一管理:线程池对线程的创建、销毁、调度进行统一管理,避免资源浪费。

1.2 为什么使用线程池?

使用线程池可以带来以下好处:

  1. 避免频繁创建销毁线程的开销

    • 每次创建线程需要分配资源,如内存堆栈、CPU 调度等,销毁线程也需要一定的代价。
    • 线程池通过线程复用机制,减少了这种频繁操作的系统开销。
  2. 限制并发数量,防止系统过载

    • 无限制地创建线程可能导致系统资源耗尽,甚至引发宕机。
    • 线程池通过设置最大线程数量,合理控制并发任务数。
  3. 提高任务响应速度

    • 线程池中的线程是提前创建好的,当有新任务时可以立即分配线程执行,而无需等待线程创建。
  4. 统一管理任务队列和线程生命周期

    • 线程池可以管理任务的执行顺序,提供优先级支持,并统一处理线程的生命周期。

1.3 线程池的工作原理

线程池的基本工作流程如下:

  1. 任务提交

    • 用户将任务提交给线程池,任务可以是实现了 RunnableCallable 接口的对象。
    • 线程池会将任务放入任务队列中等待执行。
  2. 线程分配

    • 如果线程池中有空闲线程,则立即分配线程执行任务。
    • 如果没有空闲线程,且线程池未达到最大线程数,则创建新线程来处理任务。
    • 如果线程数已达上限,任务会进入等待队列,直到有空闲线程可用。
  3. 任务执行

    • 被分配的线程从任务队列中取出任务,调用 run()call() 方法来执行任务逻辑。
  4. 线程回收

    • 任务执行完毕后,线程不会被销毁,而是返回线程池等待下一次任务分配。
    • 如果线程长时间空闲且超出线程池的核心线程数限制,线程会被销毁以节约资源。

工作原理图

任务提交 → 加入任务队列 → 分配线程 → 任务执行 → 线程回收

1.4 线程池的核心参数

线程池的行为主要由以下核心参数控制:

  1. 核心线程数(corePoolSize)

    • 线程池中始终保持的线程数量,即使线程处于空闲状态也不会回收。
    • 适合设置为系统平均负载的并发数量。
  2. 最大线程数(maximumPoolSize)

    • 线程池中允许创建的最大线程数量,控制资源消耗的上限。
  3. 任务队列(workQueue)

    • 用于保存待执行任务的队列类型,如 ArrayBlockingQueueLinkedBlockingQueue
  4. 线程存活时间(keepAliveTime)

    • 超过核心线程数的线程在空闲状态下能存活的时间,适用于动态调整线程池规模。
  5. 线程工厂(ThreadFactory)

    • 定制线程的创建方式,如线程命名、优先级设置等。
  6. 任务拒绝策略(RejectedExecutionHandler)

    • 当线程池及其队列都满了时,如何处理新提交的任务:
      • AbortPolicy:直接抛出异常(默认策略)。
      • CallerRunsPolicy:由提交任务的线程自己执行任务。
      • DiscardPolicy:直接丢弃任务,不抛异常。
      • DiscardOldestPolicy:丢弃队列中最老的任务。

2. 提交任务的方法

线程池的任务提交方式主要分为以下两种,分别对应不同的应用场景和返回值需求。

2.1 提交方式一:execute() 方法

execute() 是线程池的基本任务提交方法,适用于不需要任务执行结果的场景。

方法定义

void execute(Runnable command)

工作原理

  1. 将任务提交到线程池。
  2. 如果线程池中有空闲线程,立即执行任务。
  3. 如果没有空闲线程,任务将被放入任务队列。
  4. 如果队列已满且线程数已达最大值,则触发拒绝策略。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecuteExample {public static void main(String[] args) {// 创建一个固定大小为3的线程池ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交任务for (int i = 1; i <= 5; i++) {int taskId = i;executorService.execute(() -> {System.out.println("任务 " + taskId + " 正在执行,线程:" + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务耗时} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("任务 " + taskId + " 执行完毕");});}// 关闭线程池executorService.shutdown();}
}

输出示例

任务 1 正在执行,线程:pool-1-thread-1
任务 2 正在执行,线程:pool-1-thread-2
任务 3 正在执行,线程:pool-1-thread-3
任务 1 执行完毕
任务 4 正在执行,线程:pool-1-thread-1
任务 2 执行完毕
...

优点

  • 简单快捷。
  • 适合不需要返回值的任务。

注意

  • 无法直接获取任务的执行结果。
  • 如果任务中抛出异常,可能无法感知。

2.2 提交方式二:submit() 方法

submit() 方法更灵活,可以用来提交 RunnableCallable 任务,并返回一个 Future 对象,以便在后续获取任务执行结果。

方法定义

<T> Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)

工作原理

  1. 将任务提交到线程池。
  2. 任务执行后,结果会存储在 Future 对象中。
  3. 可以通过 Futureget() 方法获取结果,或在任务未完成时进行阻塞等待。

示例代码

import java.util.concurrent.*;public class SubmitExample {public static void main(String[] args) throws InterruptedException, ExecutionException {// 创建一个固定大小为2的线程池ExecutorService executorService = Executors.newFixedThreadPool(2);// 提交 Runnable 任务Future<?> runnableFuture = executorService.submit(() -> {System.out.println("Runnable 任务正在执行,线程:" + Thread.currentThread().getName());});// 提交 Callable 任务Future<String> callableFuture = executorService.submit(() -> {System.out.println("Callable 任务正在执行,线程:" + Thread.currentThread().getName());return "任务结果";});// 等待任务完成并获取结果System.out.println("Callable 任务的返回值:" + callableFuture.get());// 关闭线程池executorService.shutdown();}
}

输出示例

Runnable 任务正在执行,线程:pool-1-thread-1
Callable 任务正在执行,线程:pool-1-thread-2
Callable 任务的返回值:任务结果

优点

  • 支持返回结果(适用于 Callable)。
  • 可以通过 Future 检测任务完成状态或取消任务。

注意

  • get() 方法会阻塞当前线程,直到任务完成。
  • 如果任务抛出异常,Future.get() 会抛出 ExecutionException

2.3 两种提交方法的对比

特性execute()submit()
任务类型仅支持 Runnable支持 RunnableCallable
返回值无返回值返回 Future,可获取结果或检查状态
任务抛出异常的处理方式可能被忽略通过 Future.get() 抛出异常
使用场景不关心任务结果的简单任务需要获取任务结果或处理异常的场景

小结

  • execute() 是基础的任务提交方式,适合无结果任务。
  • submit() 更灵活,适合需要结果或异常处理的任务。

3. 四种线程池的创建

        Java 提供了 Executors 工具类,用于快速创建常见类型的线程池。这些线程池类型根据不同的需求场景设计,可以有效管理线程的创建和回收。

3.1 创建方式一:固定大小线程池(FixedThreadPool)

特点

  • 拥有固定数量的线程。
  • 无论有多少任务,线程池中的线程数量始终保持不变。
  • 任务超出线程数量时,将进入任务队列等待执行。

适用场景

  • 稳定且长期运行的任务,线程数量可根据系统资源确定。

创建方式

ExecutorService executorService = Executors.newFixedThreadPool(int nThreads);

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolExample {public static void main(String[] args) {// 创建固定大小为3的线程池ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交5个任务for (int i = 1; i <= 5; i++) {int taskId = i;executorService.execute(() -> {System.out.println("任务 " + taskId + " 正在执行,线程:" + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务耗时} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池executorService.shutdown();}
}

运行效果: 固定数量的线程执行任务,其余任务在队列中等待:

任务 1 正在执行,线程:pool-1-thread-1
任务 2 正在执行,线程:pool-1-thread-2
任务 3 正在执行,线程:pool-1-thread-3
任务 4 等待执行
任务 5 等待执行

3.2 创建方式二:单线程线程池(SingleThreadExecutor)

特点

  • 只有一个线程在执行任务。
  • 所有任务会按照提交的顺序(FIFO,先进先出)执行。
  • 适合需要顺序执行任务的场景。

适用场景

  • 确保任务按顺序执行。
  • 不需要并发操作时,例如日志记录。

创建方式

ExecutorService executorService = Executors.newSingleThreadExecutor();

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadExecutorExample {public static void main(String[] args) {// 创建单线程线程池ExecutorService executorService = Executors.newSingleThreadExecutor();// 提交3个任务for (int i = 1; i <= 3; i++) {int taskId = i;executorService.execute(() -> {System.out.println("任务 " + taskId + " 正在执行,线程:" + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池executorService.shutdown();}
}

运行效果: 任务严格按顺序执行:

任务 1 正在执行,线程:pool-1-thread-1
任务 2 正在执行,线程:pool-1-thread-1
任务 3 正在执行,线程:pool-1-thread-1

3.3 创建方式三:缓存线程池(CachedThreadPool)

特点

  • 线程数量不固定,动态调整。
  • 如果有空闲线程可用,则复用空闲线程;如果没有空闲线程,则创建新线程。
  • 空闲线程超过 60 秒未使用会被销毁。

适用场景

  • 大量短期任务,且任务数量不确定。

创建方式

ExecutorService executorService = Executors.newCachedThreadPool();

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CachedThreadPoolExample {public static void main(String[] args) {// 创建缓存线程池ExecutorService executorService = Executors.newCachedThreadPool();// 提交10个任务for (int i = 1; i <= 10; i++) {int taskId = i;executorService.execute(() -> {System.out.println("任务 " + taskId + " 正在执行,线程:" + Thread.currentThread().getName());});}// 关闭线程池executorService.shutdown();}
}

运行效果: 任务数量多时,会快速创建新线程:

任务 1 正在执行,线程:pool-1-thread-1
任务 2 正在执行,线程:pool-1-thread-2
...
任务 10 正在执行,线程:pool-1-thread-10

3.4 创建方式四:定时线程池(ScheduledThreadPool)

特点

  • 用于执行定时任务或周期性任务。
  • 可以延迟执行任务或按照固定时间间隔循环执行任务。

适用场景

  • 周期性任务调度,例如定时备份、定时发送邮件。

创建方式

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(int corePoolSize);

示例代码

import java.util.concurrent.*;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建定时线程池ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);// 延迟3秒执行任务scheduledExecutorService.schedule(() -> {System.out.println("延迟执行任务,线程:" + Thread.currentThread().getName());}, 3, TimeUnit.SECONDS);// 每2秒执行一次任务scheduledExecutorService.scheduleAtFixedRate(() -> {System.out.println("周期性任务,线程:" + Thread.currentThread().getName());}, 1, 2, TimeUnit.SECONDS);}
}

运行效果

  • 延迟 3 秒后执行一次任务。
  • 每 2 秒执行一次周期性任务。

小结

类型特点适用场景
FixedThreadPool固定线程数量,任务超出进入队列稳定并发任务
SingleThreadExecutor单线程,顺序执行任务需要任务严格按顺序执行的场景
CachedThreadPool线程数量动态调整短期、大量任务,任务数不确定
ScheduledThreadPool支持定时和周期性任务定时任务和循环任务

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用LINUX的dd命令制作自己的img镜像

为了避免重复安装同一镜像&#xff0c;配置环境&#xff0c;首先我准备一个正常使用的完整系统。 使用Gparted软件先将母盘&#xff08;如U盘&#xff0c;TF卡&#xff09;分区调整为只有数据的大小。如&#xff1a;60G的TF卡&#xff0c;只用了3.5G&#xff0c;将未使用的空间…

doris:基于 Arrow Flight SQL 的高速数据传输链路

Doris 基于 Arrow Flight SQL 协议实现了高速数据链路&#xff0c;支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途​ 从 Doris 加载大批量数据到其他组件&#xff0c;如 Python/Java/Spark/Flink&#xff0c;可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…

Gitee图形界面上传(详细步骤)

目录 1.软件安装 2.安装顺序 3.创建仓库 4.克隆远程仓库到本地电脑 提交代码的三板斧 1.软件安装 Git - Downloads (git-scm.com) Download – TortoiseGit – Windows Shell Interface to Git 2.安装顺序 1. 首先安装git-2.33.1-64-bit.exe&#xff0c;顺序不能搞错2. …

用公网服务代理到本地电脑笔记

参考&#xff1a; 利用frp 穿透到内网的http/https网站&#xff0c;实现对外开放&#xff08;这篇博客有点老&#xff0c;需要改动&#xff0c;不能照抄&#xff09;&#xff1a;https://www.cnblogs.com/hahaha111122222/p/8509150.html frp内网穿透(windows和服务器)&#xf…

(leetcode算法题)384. 打乱数组 398. 随机数索引

问题转化&#xff1a; 题目要求将nums中的数字出现的次序随机打乱 转化成&#xff1a;对于 0 号位置来说&#xff0c;nums[i], ..., nums[n - 1] 可以等概率的出现 ... && ... && 对于 n - 1号位置来说&#xff0c;nums[i], ..., nums[n - 1] 可以等概率的出…

Redis - 5 ( 18000 字 Redis 入门级教程 )

一&#xff1a; 补充知识 1.1 渐进式遍历 Redis 使用 scan 命令以渐进式方式遍历键&#xff0c;避免了直接使用 keys 命令可能引发的阻塞问题。scan 的时间复杂度为 O(1)&#xff0c;但需要多次执行才能完成对所有键的遍历&#xff0c;整个过程分步进行&#xff0c;有效减少阻…

22408操作系统期末速成/复习(考研0基础上手)

第一部分:计算题&#xff1a; 考察范围&#xff1a;&#xff08;标红的是重点考&#xff09; 第一章&#xff1a;CPU利用率&#xff1a; 第二章&#xff1a; 进程调度算法&#xff08;需要注意不同调度算法的优先级和题目中给出的是否可以抢占【分为可抢占和不可抢占&#xff…

AI在电子制造中的应用:预测质量控制

一、 电子制造中存在的质量问题 电子制造过程中&#xff0c;由于生产工艺复杂、材料种类繁多、生产环境要求高等因素&#xff0c;可能会出现各种质量问题。 常见质量问题如下&#xff1a; 1. 空焊 原因&#xff1a;锡膏活性较弱、钢网开孔不佳、铜铂间距过大或大铜贴小元件、…

如何通过API实现淘宝商品评论数据抓取?item_review获取淘宝商品评论

前几天一个好朋友要我帮忙抓一下淘宝商品的评论数据&#xff0c;获取淘宝评论数据可以帮忙商家们做好市场调研&#xff0c;对自己的产品进行升级&#xff0c;从而更好地获取市场。我将详细爬取方法封装成API&#xff0c;以供方便调用。 item_review-获得淘宝商品评论 响应示例…

springboot550乐乐农产品销售系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统乐乐农产品销售系统信息管理难度大&#xff0c;容错率低&…

百度贴吧的ip属地什么意思?怎么看ip属地

在数字化时代&#xff0c;IP地址不仅是网络设备的唯一标识符&#xff0c;更承载着用户的网络身份与位置信息。百度贴吧作为广受欢迎的社交平台&#xff0c;也遵循相关规定&#xff0c;在用户个人主页等位置展示账号IP属地信息。那么&#xff0c;百度贴吧的IP属地究竟意味着什么…

[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)

简介&#xff1a;从20世纪90年代开始&#xff0c;利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高&#xff0c;传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…

element-plus大版本一样,但是小版本不一样导致页面出bug

npm 的版本 node的版本 npm的源这些都一样&#xff0c;但是效果不一样 发现是element的包版本不一样导致的 2.9.1与2.8.1的源是不一样的&#xff0c;导致页面出bug;

【网络协议】开放式最短路径优先协议OSPF详解(一)

OSPF 是为取代 RIP 而开发的一种无类别的链路状态路由协议&#xff0c;它通过使用区域划分以实现更好的可扩展性。 文章目录 链路状态路由协议OSPF 的工作原理OSPF 数据包类型Dijkstra算法、管理距离与度量值OSPF的管理距离OSPF的度量值 链路状态路由协议的优势拓扑结构路由器O…

《数据结构》期末考试测试题【中】

《数据结构》期末考试测试题【中】 21.循环队列队空的判断条件为&#xff1f;22. 单链表的存储密度比1&#xff1f;23.单链表的那些操作的效率受链表长度的影响&#xff1f;24.顺序表中某元素的地址为&#xff1f;25.m叉树第K层的结点数为&#xff1f;26. 在双向循环链表某节点…

华为数通考试模拟真题(附带答案解析)题库领取

【多选题】 管理员想要更新华为路由器的VRP版本&#xff0c;则正确的方法有? A管理员把路由器配置为FTP服务器&#xff0c;通过FTP来传输VRP软件 B:管理员把路由器置为FTP客户端&#xff0c;通过FTP来传输VRP软件 C:管理员把路由器配置为TFTP客户端&#xff0c;通过TFTP来传…

Linux:操作系统不朽的传说

操作系统是计算机的灵魂&#xff0c;它掌控着计算机的硬件和软件资源&#xff0c;为用户和应用程序提供了一个稳定、高效、安全的运行环境。 在众多操作系统中&#xff0c;Linux 的地位举足轻重。它被广泛应用于服务器、云计算、物联网、嵌入式设备等领域。Linux 的成功离不开…

前端(API)学习笔记(CLASS 4):进阶

1、日期对象 日期对象&#xff1a;用来表示事件的对象 作用&#xff1a;可以得到当前系统时间 1、实例化 在代码中发现了new关键字&#xff0c;一般将这个操作称为实例化 创建一个时间对象并获取时间 获得当前时间 const datenew Date() 使用日志查看&#xff0c;得到的…

【USRP】教程:在Macos M1(Apple芯片)上安装UHD驱动(最正确的安装方法)

Apple芯片 前言安装Homebrew安装uhd安装gnuradio使用b200mini安装好的路径下载固件后续启动频谱仪功能启动 gnu radio关于博主 前言 请参考本文进行安装&#xff0c;好多人买了Apple芯片的电脑&#xff0c;这种情况下&#xff0c;可以使用UHD吗&#xff1f;答案是肯定的&#…

SAP 01-初识AMDP(ABAP-Managed Database Procedure)

1. 什么是AMDP(ABAP-Managed Database Procedure) 1.&#xff09;AMDP - ABAP管理数据库程序&#xff0c;是一种程序&#xff0c;我们可以使用SQLSCRIPT在AMDP内部编写代码&#xff0c;SQLSCRIPT是一种与SQL脚本相同的数据库语言&#xff0c;这种语言易于理解和编码。 将AM…