面试10000次依然会问的【线程池】,你还不会?

线程池的基本概念

线程池是一种基于池化技术的线程使用方式,它允许我们有效地管理和复用线程,减少线程的创建和销毁的开销,从而提高系统的响应速度。在Java中,线程池的管理主要通过ThreadPoolExecutor类来实现。

线程池的定义与实现

线程池(ThreadPool)本质上是一个管理线程的集合,它包含了一个任务队列和一组工作线程。任务队列用于存放等待执行的任务,工作线程则负责执行这些任务。在Java中,ThreadPoolExecutor类提供了丰富的构造函数,允许我们详细配置线程池的各个参数,以适应不同的使用场景。

如何提高系统响应速度

线程池通过减少每个任务执行时创建和销毁线程的开销,提高了响应速度并实现了线程的重复利用。当任务被提交到线程池时,线程池会首先尝试使用空闲的核心线程(core threads)去执行任务,如果核心线程都在忙碌,任务会被放入工作队列中等待。如果工作队列已满,且当前线程数量小于最大线程数(maximumPoolSize),线程池会创建新的线程来处理任务。这种动态的线程管理策略使得线程池可以根据任务的数量动态调整线程的数量,从而使系统资源得到有效利用。

线程的管理

线程池中的线程分为核心线程和非核心线程。核心线程会一直存活,即使它们没有任务执行。而非核心线程如果空闲时间超过了keepAliveTime,就会被终止以释放资源。这样的设计保证了线程池可以在处理不同负载的任务时,保持足够的灵活性和高效性

线程池的使用减少了每次任务调用的开销,因为线程的创建和销毁都是有成本的,特别是在任务数量巨大时。通过重用已经存在的线程,线程池显著提高了程序的响应速度,同时也提供了更好的系统资源管理和更低的系统开销

核心参数解析

  1. corePoolSize(核心线程数)

    • 定义:线程池的基本大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了之后才会创建超过此数量的线程。
    • 作用:决定了线程池的最小线程数,这些线程不会因为空闲时间超时而被回收。
    • 示例代码
      int corePoolSize = 2; // 设置核心线程数为2
      
  2. maximumPoolSize(最大线程数)

    • 定义:线程池允许创建的最大线程数。
    • 作用:控制线程池中最大并发执行的线程数,当工作队列满时,线程池会创建新线程来处理任务,直到线程数达到maximumPoolSize。
    • 示例代码
      int maximumPoolSize = 4; // 设置最大线程数为4
      
  3. keepAliveTime(非核心线程的超时时长)

    • 定义:当线程数大于corePoolSize时,多余的空闲线程存活的最长时间。
    • 作用:非核心线程在空闲状态下的最大存活时间,超过这个时间非核心线程将被终止。
    • 示例代码
      long keepAliveTime = 60; // 设置非核心线程的空闲存活时间为60秒
      
  4. TimeUnit(超时时长的时间单位)

    • 定义:keepAliveTime的时间单位。
    • 作用:指定keepAliveTime的单位,常用的单位有毫秒、秒、分钟等。
    • 示例代码
      TimeUnit unit = TimeUnit.SECONDS; // 设置时间单位为秒
      
  5. BlockingQueue workQueue(任务队列)

    • 定义:用来存储待执行任务的阻塞队列。
    • 作用:存放提交但尚未被执行的任务。它可以选择不同类型的队列,如无界队列、有界队列等。
    • 示例代码
      BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1024); // 使用容量为1024的LinkedBlockingQueue
      
  6. ThreadFactory(线程工厂)

    • 定义:用于设置创建线程的工厂。
    • 作用:可以通过自定义ThreadFactory来改变线程的创建方式,如设置线程名、优先级等。
    • 示例代码
      ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 使用默认线程工厂
      
  7. RejectedExecutionHandler(饱和策略)

    • 定义:当阻塞队列和最大线程池都满时,用于处理新提交的任务。
    • 作用:定义线程池的饱和策略,如直接丢弃、抛出异常、尝试其他线程池等。
    • 示例代码
      RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); // 使用CallerRunsPolicy饱和策略
      

示例代码整合

import java.util.concurrent.*;public class ThreadPoolDemo {public static void main(String[] args) {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, // corePoolSize4, // maximumPoolSize60L, // keepAliveTimeTimeUnit.SECONDS, // unitnew LinkedBlockingQueue<Runnable>(1024), // workQueueExecutors.defaultThreadFactory(), // threadFactorynew ThreadPoolExecutor.CallerRunsPolicy() // handler);executor.execute(new Runnable() {@Overridepublic void run() {System.out.println("任务执行");}});executor.shutdown();}
}

我们创建了一个ThreadPoolExecutor实例,配置了核心线程数、最大线程数、非核心线程的存活时间等参数,并提交了一个任务来演示线程池的使用。通过这个示例,我们可以看到ThreadPoolExecutor类的核心参数是如何在实际中被应用的。

线程池大小配置

线程池大小配置是一个至关重要的决策,因为它直接影响到程序的性能和资源利用率。在Java中,通过ThreadPoolExecutor类来实现线程池的管理,其中涉及到几个关键的参数:corePoolSizemaximumPoolSizekeepAliveTime等。

CPU密集型任务

对于CPU密集型的任务,线程池的大小应该尽量小。这类任务的特点是它们需要大量的CPU时间来计算数据,而几乎不会有I/O操作(如读写文件、数据库操作等)。因此,线程池的大小一般推荐设置为处理器核心数加1(NCPU+1),这样可以让CPU的时间片尽可能地被利用,同时避免了线程切换带来的开销。

例如,如果一个服务器有4个CPU核心,那么线程池的corePoolSizemaximumPoolSize可以设置为5。

int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
int maximumPoolSize = corePoolSize;
long keepAliveTime = 0L; // 当线程数大于corePoolSize时,这个配置通常设置为0
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();ThreadPoolExecutor cpuIntensivePool = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue
);
IO密集型任务

IO密集型任务则不同,它们需要等待I/O操作的完成,CPU计算只占用了少部分时间。在这种情况下,可以配置更多的线程,以便在某些线程等待I/O操作时,其他线程可以继续执行。通常设置线程数为处理器核心数的两倍(2 * NCPU)。

如果服务器有4个CPU核心,那么线程池的corePoolSize可以设置为8,而maximumPoolSize可以设置得更高,以便在高峰时段处理更多的并发任务。

int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
int maximumPoolSize = corePoolSize * 2;
long keepAliveTime = 60L; // 非核心线程的空闲存活时间可以设置得长一些
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();ThreadPoolExecutor ioIntensivePool = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue
);

在配置线程池时,还需要考虑任务的实际情况和系统资源的限制,以避免创建过多的线程导致资源耗尽。实践中,应该不断调整这些参数,通过监控和性能测试来找到最优的配置。

线程池类型概览

线程池(ThreadPool)是一种基于池化技术的线程使用方式,它允许多个任务共享一个固定的线程集合,而不是为每个任务创建新的线程。在Java中,通过ThreadPoolExecutor类及其工厂方法Executors来实现线程池的管理。以下是Java中几种常用线程池的类型及其特点:

FixedThreadPool

FixedThreadPool拥有固定线程数量的线程池,适用于负载较重的服务器。它可以限制当前线程数量,有助于防止资源的过度消耗。当所有线程都在活动时,新任务将在队列中等待,直到有线程可用。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
fixedThreadPool.execute(() -> {// 任务代码
});
CachedThreadPool

CachedThreadPool是一个线程数无固定上限的线程池,适合短生命周期的异步任务。它能够在需要时创建新线程,并在线程空闲一定时间后销毁这些线程,从而合理地管理线程存活时间。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(() -> {// 任务代码
});
SingleThreadExecutor

SingleThreadExecutor是单线程的Executor,用于需要保证顺序执行的场景。它确保所有任务都在同一个线程中按顺序执行,这样可以避免多线程并发问题。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(() -> {// 任务代码
});
ScheduledThreadPool

ScheduledThreadPool用于延迟或定期执行任务的线程池,适合需要多个后台线程执行周期任务的应用场景。它可以安排在将来某个时间执行任务或者定期执行任务。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(() -> {// 任务代码
}, 3, TimeUnit.SECONDS);

每种类型的线程池都有其适用的场景。例如,FixedThreadPool适用于资源受限的情况,而CachedThreadPool适合执行大量的短期异步任务。SingleThreadExecutor适用于需要顺序执行任务的场景,而ScheduledThreadPool适合执行定时或周期性的任务。

在选择线程池类型时,应考虑任务的性质(CPU密集型、IO密集型或混合型)、任务的数量以及任务的执行时间等因素。合理的线程池配置能够提高程序性能,避免资源浪费,并保证系统的稳定性。

TimeUnit的枚举类型详解

TimeUnit是Java中表示时间单位的一个枚举类型,它在线程池中主要用于定义非核心线程的空闲存活时间。这个枚举类型提供了多种时间单位选项,从纳秒(NANOSECONDS)到天(DAYS),以适应不同的时间精度需求。

ThreadPoolExecutor构造函数中,keepAliveTimeTimeUnit参数配合使用,定义了非核心线程在没有任务执行时可以存活的最长时间。这个设置对于线程池的资源管理非常关键,因为它决定了当线程池的线程数量超过核心线程数时,多余的线程在多长时间内可以被保留。

示例代码

以下是一个Java代码示例,展示了如何在创建ThreadPoolExecutor时指定非核心线程的keepAliveTimeTimeUnit

import java.util.concurrent.TimeUnit;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;public class ThreadPoolExample {public static void main(String[] args) {// 创建线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(1, // corePoolSize: 核心线程数10, // maximumPoolSize: 最大线程数60L, // keepAliveTime: 非核心线程空闲存活时间TimeUnit.SECONDS, // unit: 时间单位new SynchronousQueue<Runnable>() // workQueue: 任务队列);// ... 提交任务等操作}
}

在这个示例中,非核心线程的存活时间被设置为60秒。如果一个非核心线程空闲时间超过了这个时间,那么这个线程将会被终止并从线程池中移除,这样可以避免资源的浪费。

通过合理配置keepAliveTimeTimeUnit,开发者可以根据实际的业务需求和系统资源的限制,优化线程池的性能和资源利用率。

线程池的使用建议

  1. 合理配置线程池大小:根据任务的类型和系统资源情况来调整线程池的配置。对于CPU密集型任务,线程数可以设置为CPU核心数加1;而对于IO密集型任务,线程数可以设置为2倍的CPU核心数。

  2. 选择合适的线程池类型:Java提供了几种线程池,如FixedThreadPoolCachedThreadPoolSingleThreadExecutorScheduledThreadPool。选择适合任务特性的线程池类型,例如,FixedThreadPool适用于负载较重的服务器,而CachedThreadPool适合执行大量短期异步任务。

  3. 使用合适的饱和策略:当线程池和工作队列都满时,应选择合适的饱和策略(RejectedExecutionHandler),如CallerRunsPolicy,这对于保证线程池稳定性和系统资源的有效利用至关重要。

  4. 优雅关闭线程池:在应用程序结束时,应该优雅地关闭线程池,调用shutdown()方法来完成已提交的任务而不接受新任务,或者shutdownNow()来尝试停止所有正在执行的任务并立即关闭线程池。

  5. 避免资源耗尽:特别是在使用CachedThreadPool时,由于线程数没有限制,需要注意控制最大线程数,以避免创建过多线程导致的资源耗尽。

  6. 监控线程池状态:定期监控线程池的状态,包括活跃线程数、完成任务数以及队列中等待的任务数,这有助于了解线程池的工作情况并及时调整配置。

  7. 异常处理:确保任务执行中的异常能够被捕获和处理,避免因异常导致线程终止而影响线程池中其他任务的执行。

线程池参数的作用和执行流程

线程池(ThreadPoolExecutor)是用于并发执行任务的一组线程的集合。它通过减少在每个任务执行时创建和销毁线程的开销,提高了响应速度并实现了线程的重复利用。线程池的工作由几个核心参数控制:

  1. corePoolSize:线程池的基本大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了之后才会增加线程数。
  2. maximumPoolSize:线程池允许创建的最大线程数。
  3. keepAliveTime:当线程数大于corePoolSize时,这是多余空闲线程在终止前等待新任务的最长时间。
  4. unit:keepAliveTime的时间单位。
  5. workQueue:用于保存等待执行的任务的阻塞队列。
  6. threadFactory:执行程序创建新线程时使用的工厂。
执行流程

当一个任务被提交到线程池时,线程池会根据以下流程处理任务:

  1. 如果当前运行的线程数少于corePoolSize,则线程池会创建一个新的线程来执行提交的任务,即使其他工作线程处于空闲状态。
  2. 如果运行的线程数达到了corePoolSize,但是队列未满,任务将被放入队列中。
  3. 如果队列已满,而运行的线程数少于maximumPoolSize,则线程池会再次尝试创建新的线程。
  4. 如果线程数已经达到maximumPoolSize,线程池会执行拒绝策略,拒绝接受新任务。
示例代码(Java)
// 创建具有给定初始参数的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, // corePoolSize4, // maximumPoolSize60, // keepAliveTimeTimeUnit.SECONDS, // unitnew LinkedBlockingQueue<>(1024), // workQueueExecutors.defaultThreadFactory(), // threadFactorynew ThreadPoolExecutor.AbortPolicy() // handler
);// 提交任务到线程池
executor.execute(() -> {System.out.println("任务执行");
});// 关闭线程池
executor.shutdown();

我们创建了一个核心线程数为2,最大线程数为4,非核心线程的空闲存活时间为60秒的线程池。使用execute方法提交一个简单的打印任务到线程池。使用shutdown方法平滑地关闭线程池,不再接受新任务,同时等待已提交的任务执行完成。

总结

线程池的合理配置对于提升系统性能、优化资源利用具有至关重要的作用。在Java并发编程中,通过精细调整线程池的各项参数,可以有效地管理线程生命周期,减少线程创建和销毁的开销,从而加快系统响应速度。

但你要记住没有一成不变的最佳实践,每个应用场景的需求都有所不同。因此,强烈鼓励开发者在实际开发过程中,结合具体业务需求和系统负载情况,不断试验和调整,以便找到最适合当前应用的线程池配置。

只有通过实践,我们才能深入理解线程池的工作原理,才能充分发挥其强大的功能,为我们的应用程序带来最佳的性能表现。

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

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

相关文章

IntelliJ IDEA - Git Commit 后 Commit 窗口不消失解决方案

这个现象是在 2023 年版本后开始的&#xff0c;一开始以为是 Mac 系统的原因&#xff0c;后来发现原来 Windows 也这样&#xff0c;所以应该只跟 IDEA 版本有关 可以看到左侧 commit 后&#xff0c;这个侧边栏还在&#xff0c;按理讲在以前的版本是之前消失&#xff0c;这样使…

SOLIDWORKS 2024新功能之Visualize篇

SOLIDWORKS 2024新功能Visualize 增强了创建引人注目的外观的功能 SOLIDWORKS Visualize 使用 Dassault Systmes 的企业 PBR 着色模型 (DSPBR) 来准确复制金属、玻璃、塑料和其他曲面的逼真外观。 DSPBR 是材料模型&#xff0c;用于基于物理的渲染&#xff0c;受 3DEXPERIENCE…

动态规划(3)---Leetcode509.斐波那契数

题目 分析 很明显的动态规划&#xff0c;直接写出。之前都是用递归来写。 题解 class Solution {public int fib(int n) {if (n0) return 0;if (n1) return 1;int q0,p1,r0;for(int i2;i<n;i){rqp;int tmpp;pr;qtmp; }return r;}

GIT的安装与常见命令

Git的介绍 Git是一个开源的分布式版本控制系统&#xff0c;最初由Linus Torvalds在2005年创建用于管理Linux内核的开发&#xff0c;现在已成为全球最流行的版本控制工具之一。 Git可以跟踪代码的修改&#xff0c;记录开发历程&#xff0c;保证多人合作开发时代码的一致性&…

如何在Android平板上远程连接Ubuntu服务器code-server进行代码开发?

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机&#xff0c;Ubuntu或者centos都可以&#xff0c;这里以VMwhere ubuntu系统为例 下载code serve…

探索人工智能领域——30个名词详解

目录 前言 正文 总结​​​​​​​ &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请…

微信小程序:js实现不改变原数组的情况下增加一条对象到新数组中

效果 核心 old_array.slice(0) 表示对 old_array 这个数组进行切片操作&#xff0c;从索引 0 开始&#xff08;包括索引 0&#xff09;&#xff0c;直到数组的末尾&#xff0c;old_array.slice(0) 中的 0 表示开始切片的索引位置&#xff0c;而由于没有传入第二个参数&#xff…

电商项目之Java8函数式接口落地实践

文章目录 1 问题背景2 前言3 多处重复的重试机制代码4 优化后的代码5 进一步优化 1 问题背景 在电商场景中&#xff0c;会调用很多第三方的云服务&#xff0c;比如发送邮件、发起支付、发送验证码等等。由于网络存在抖动&#xff0c;有时候发起调用后会拿到500的状态码&#xf…

Code Review最佳实践

Code Review最佳实践 Code Review 我一直认为Code Review&#xff08;代码审查&#xff09;是软件开发中的最佳实践之一&#xff0c;可以有效提高整体代码质量&#xff0c;及时发现代码中可能存在的问题。包括像Google、微软这些公司&#xff0c;Code Review都是基本要求&…

【数据结构】树与二叉树(七):二叉树的遍历

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语5.1.4 树的表示 5.2 二叉树5.2.1 二叉树1. 定义2. 特点3. 性质引理5.1&#xff1a;二叉树中层数为i的结点至多有 2 i 2^i 2i个&#xff0c;其中 i ≥ 0 i \geq 0 i≥0。引理5.2&#xff1a;高度为k的二叉…

代挂单页网址发布页+加盟代理+APP下载页源码

代挂单页加盟代理网址发布页app下载页HTML单页版本&#xff0c;自行修改源码内文字。自行修改联系方式、登录地址&#xff01;上传即可使用。源码我已全部打包好&#xff0c;直接上传本站提供的源码&#xff0c;无后台&#xff0c;直接访问即可&#xff01; 源码下载&#xff…

EF Core 数据库映射成实体类

首先在 NuGet 包管理器中安装三个包 Microsoft.EntityFrameworkCore.SqlServer 是一个用于与 SQL Server 数据库进行交互的实体框架核心包。这个包提供了方便的方法和工具&#xff0c;用于在 .NET Core 应用程序中操作 SQL Server 数据库。 Microsoft.EntityFrameworkCore.Too…

fmx windows 下 制作无边框窗口最小化最大化并鼠标可拖移窗口

1,最顶端 放一个rectangle 置顶 ,此区域后面实现鼠标拖动 移动窗口,可在上面放置最大,最小,关闭按钮 2,窗口边框模式 设置 none 3,rectangel mousemove事件 uses Winapi.Windows,Winapi.Messages,FMX.Platform.Winprocedure TfrmMain.Rectangle1MouseMove(Sender: TObje…

[直播自学]-[汇川easy320]搞起来(3)看文档安装软件 查找设备

2023.11.09 20&#xff1a;04 按照文档 解压压缩包得到&#xff1a; 打开 里面有一条值得注意&#xff1a; 想把软件安装到C盘&#xff0c;但是C盘没什么空间了&#xff0c;把C盘清理清理。 20&#xff1a;35 安装很快完成&#xff0c;然后阅读 由于PLC是新的&#xff0c…

Postgresql数据类型-布尔类型

前面介绍了PostgreSQL支持的数字类型、字符类型、时间日期类型&#xff0c;这些数据类型是关系型数据库的常规数据类型&#xff0c;此外PostgreSQL还支持很多非常规数据类型&#xff0c;比如布尔类型、网络地址类型、数组类型、范围类型、json/jsonb类型等&#xff0c;从这一节…

Bean作用域

从笔者之前的博客&#xff0c;我们可以看出 Spring 是⽤来读取和存储 Bean&#xff0c;因此在 Spring 中 Bean 是最核⼼的操作 资源&#xff0c;所以接下来我们深⼊学习⼀下 Bean 对象&#xff1a;Bean作用域&#xff01; 限定程序中变量的可用范围叫做作用域&#xff01;或者…

scss 实用教程

变量 $ 定义变量 $link-color: blue;变量名可以与css中的属性名和选择器名称相同 使用变量 a {color: $link_color; }$highlight-border: 1px solid $link_color;中划线和下划线相互兼容&#xff0c;即中划线声明的变量可以使用下划线的方式引用&#xff0c;反之亦然。 $li…

5分钟Python安装实战(MAC版本)

最近在学习Chatgpt接口&#xff0c;官方提供三种方式调用Chatgpt接口&#xff0c;分别是curl、python、node.js&#xff1a;具体介绍我放在下方图片 因为熟悉Python&#xff0c;所以我选择了python这种方式&#xff0c;顺便记录下安装过程&#xff0c;整体并不复杂&#xff0c;…

npm 下载包失败解决方案

1.【问题描述】使用 npm 下载vue项目依赖包时失败&#xff0c;版本不一致。 【解决方法】使用 npm install --force npm install --force 是一个命令行指令&#xff0c;用于在 Node.js 环境中使用 npm&#xff08;Node Package Manager&#xff09;安装包或模块。–force 参数表…

单例模式 rust和java的实现

文章目录 单例模式介绍应用实例&#xff1a;优点使用场景 架构图JAVA 实现单例模式的几种实现方式 rust实现 rust代码仓库 单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建…