剖析线程池:深入理解Java中的线程池构造和调优技巧

news2024/12/27 13:58:39

file

使用Executors工具类创建线程池

Executors的主要方法与默认配置

Executors 工具类是 Java 中创建线程池的标准方法之一,它提供了许多静态方法来创建不同类型的线程池。以下是一些常用的 Executors 方法及其作用:

  • newFixedThreadPool(int nThreads): 创建一个可重用固定线程数的线程池。
  • newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但会在之前构建的线程可用时重用它们。
  • newSingleThreadExecutor(): 创建一个使用单个 worker 线程的 Executor,确保按顺序执行任务。
  • newScheduledThreadPool(int corePoolSize): 创建一个线程池,它可以安排在给定延迟后运行命令,或定期执行命令。

默认配置 解释:

  • newFixedThreadPool 与 newSingleThreadExecutor 在默认设置下,都使用无界的工作队列,这意味着如果所有线程都在忙,新任务将在队列中等待无限长的时间。
  • newCachedThreadPool 可以无限制地创建线程,这可能会创建过多的线程,消耗过多的系统资源。
  • newScheduledThreadPool 建议只用于执行延时或定期任务。

分析Executors源码透视线程池创建过程

让我们深入分析 newFixedThreadPool 方法的源码,了解具体的创建过程:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

该方法调用了 ThreadPoolExecutor 的构造函数,设定了核心线程数 (corePoolSize) 和最大线程数 (maximumPoolSize) 都为用户指定的 nThreads。它使用一个无界的 LinkedBlockingQueue 作为工作队列,这意味着除非线程池关闭,否则线程池中的线程永远不会因为空闲而被回收。
从源码中我们可以看到,虽然 Executors 提供了便捷的创建线程池的方法,但默认的配置并不适合所有场景。特别是在任务频繁提交的情况下,无界队列可能会导致内存溢出。同时,0L 作为 keepAliveTime 参数的值,表示非核心线程不会因为空闲而被终止。

使用ThreadPoolExecutor类创建线程池

ThreadPoolExecutor构造函数解析

ThreadPoolExecutor 是线程池的核心实现类,它提供了创建一个多功能线程池的方式。构造函数中包含几个关键的参数,了解这些参数对于正确使用线程池至关重要:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize: 核心线程数 —— 即使它们是空闲的,也会始终存在于线程池中。
  • maximumPoolSize: 最大线程数 —— 线程池可支持的最大线程数量。
  • keepAliveTime 和 unit: 非核心线程的空闲存活时间,一旦超过这个时间非核心线程将被终止。
  • workQueue: 用于在执行任务之前保存任务的队列,这是一个阻塞队列。
  • threadFactory: 执行程序创建新线程时使用的工厂。
  • handler: 当任务太多来不及处理,或线程池关闭时,用于处理被拒绝的任务。

自定义ThreadPoolExecutor实践

为了展示如何自定义 ThreadPoolExecutor,以下是一个简单的示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4, // corePoolSize
    10, // maximumPoolSize
    60L, // keepAliveTime
    TimeUnit.SECONDS, // unit
    new LinkedBlockingQueue<>(100), // workQueue
    Executors.defaultThreadFactory(), // threadFactory
    new ThreadPoolExecutor.AbortPolicy() // handler
);

在这个例子中,我们创建了一个核心线程数为4,最大线程数为10,并且非核心线程的存活时间为60秒的线程池。工作队列是容量为100的 LinkedBlockingQueue。当队列满时,执行策略是 AbortPolicy,即抛出 RejectedExecutionException 异常。

源码视角分析ThreadPoolExecutor工作原理

ThreadPoolExecutor 的核心工作原理涉及几个重要的步骤:

  1. 新任务提交到线程池时,首先会检查核心线程是否全部运行,如果没有,会尝试创建一个新的核心线程。
  2. 如果核心线程已满,新任务会被加入工作队列。
  3. 如果工作队列已满,会尝试创建新的非核心线程。
  4. 如果非核心线程数已达到最大值,任务会根据拒绝策略进行处理。

整个过程是由多个组件协调完成,确保线程池的高效和稳定运行。

使用ForkJoinPool类创建线程池

ForkJoinPool的设计理念

ForkJoinPool 是专门为了优化处理递归和分治算法任务而设计的一个线程池类。不像 ThreadPoolExecutor 使用一个共享队列,ForkJoinPool 为每个工作线程提供了一个双端队列(deque),用以存储任务。这种设计可以减少线程间竞争,提高效率。
它的关键特性包括工作窃取(work-stealing)算法,工作线程可以从其他线程的队列中窃取任务来执行。这能更好地利用CPU,提高并行度。

ForkJoinPool源码剖析与使用场景

让我们来看一段使用 ForkJoinPool 的代码示例:

ForkJoinPool pool = new ForkJoinPool();

pool.execute(() -> {
    // 分解及执行并发任务
});

// 通常使用递归任务 - RecursiveAction 或 RecursiveTask

ForkJoinPool 最典型的使用场景是分解复杂任务为更小的任务,然后并行执行。它通常结合 RecursiveAction(没有返回值的任务)或 RecursiveTask(有返回值的任务)来使用;这两个类提供了 fork() 和 join() 方法,帮助我们实现任务的分解和结果的合并。
基于源码的角度,ForkJoinPool 实现了 ForkJoinWorkerThread,每个线程包含有一个任务队列。当线程中的任务执行完毕后,它会尝试从其他线程的队列窃取任务,保持高效率的执行。
下面是 ForkJoinPool 的构建方式:

ForkJoinPool fjp = new ForkJoinPool(
    Runtime.getRuntime().availableProcessors(),
    ForkJoinPool.defaultForkJoinWorkerThreadFactory,
    null, //异常处理
    false //asyncMode
);

在上面的例子中,我们创建了一个与可用处理器一样多的线程数量的 ForkJoinPool。通过设置 asyncMode 为 false,我们确保了使用 LIFO(后进先出)顺序处理任务,这对于大部分计算密集型任务来说效率更高。

使用ScheduledThreadPoolExecutor类创建线程池

ScheduledThreadPoolExecutor用法简介

ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的一个扩展,用于在给定的延迟之后执行命令,或者定期地执行命令。此类是设计为执行线程池中任务的定时或周期性调度。
下面是一些常用的方法:

  • schedule(Runnable command, long delay, TimeUnit unit): 在指定的延迟后执行任务。
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): 在指定的延迟之后开始,定期地执行任务。
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): 在指定的延迟之后开始,以固定的延迟执行任务。

深入ScheduledThreadPoolExecutor源码

ScheduledThreadPoolExecutor 使用了一个优先级队列 DelayedWorkQueue 来对任务进行排序,确保即将执行的任务位于队列的头部。以下是 ScheduledThreadPoolExecutor 延时执行任务的一个简单实例:

ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.schedule(() -> System.out.println("Task executed after 3 seconds"), 3, TimeUnit.SECONDS);

在这个例子中,我们创建了一个单线程的 ScheduledThreadPoolExecutor 实例,并提交了一个在三秒后执行的任务。
源码视角的讲解将涉及 ScheduledThreadPoolExecutor 是如何管理和执行这些安排好的任务。例如,当调用 schedule 方法时,任务将被封装为一个 ScheduledFutureTask 对象并插入到 DelayedWorkQueue 中。

定时任务与延迟执行策略

在 ScheduledThreadPoolExecutor 中,定时任务和延迟执行策略对应于两种核心方法:scheduleAtFixedRate 和 scheduleWithFixedDelay。scheduleAtFixedRate 用于执行固定频率的重复任务,即无论任务执行多长时间都尝试保持固定的时间间隔。而 scheduleWithFixedDelay 确保两次执行之间有固定的延迟,不管任务执行花费了多少时间。

线程池的选择指南

在面对不同的应用场景时,选择合适的线程池至关重要。具体选择哪种线程池,通常需要基于任务的性质以及应用的需求。

各类型线程池对比分析

  • FixedThreadPool: 适用于需要限制当前运行线程数量的场景,适用于资源消耗比较均匀时的任务。
  • CachedThreadPool: 适用于有许多短期异步任务的程序,能更快地响应外界的请求。
  • SingleThreadExecutor: 适用于需要保证执行顺序的任务,可以依次按照任务的提交顺序执行。
  • ScheduledThreadPool: 适用于需要周期性执行任务的场景,比如计划任务、定时任务。
  • ForkJoinPool: 特别适用于需要大量使用分治策略和并行计算的场景,可以充分利用多核处理器的计算能力。

线程池选择的最佳实践

最佳实践建议您考虑以下因素:

  • 任务的类别(CPU 密集型、IO 密集型、混合型)
  • 任务的优先级(是否需要快速响应)
  • 任务的执行时间(长期还是短期)
  • 资源的限制(如内存和处理器)

为了更有效率地使用线程池,通常建议自定义 ThreadPoolExecutor,这让你有机会根据自己的实际需求来调优线程池的参数。

性能调优与问题排查

调优线程池参数的建议

调整线程池大小最直接的规则是考虑到可用硬件资源和预计的任务负荷:

  • 对于CPU密集型任务,理想的线程数量大约是CPU可用核心的数量。
  • 对于IO密集型任务,则可以配置更多的线程,因为IO操作不会占用太多CPU。

另外,合适的工作队列大小和合理的拒绝策略也很关键,这可以为系统过载提供缓冲,避免资源耗尽。

常见线程池问题与排查方法

  • 线程池过大:可能会导致系统资源不足,解决办法是减小 maximumPoolSize。
  • 线程池过小:可能会导致任务排队等候,影响性能,解决办法是根据任务类型增大线程池大小。
  • 任务执行时间过长:可以考虑对任务进行分解,以减少单个任务对线程池的占用时间。
  • 内存泄漏:可能是由于长生命周期的对象被线程池引用导致的,需要使用JVM工具检测和排查。

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

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

相关文章

Git如何配合Github使用

1.安装Git https://git-scm.com/ ##2.配置 Git 安装完成后&#xff0c;你需要设置 Git 的用户名和邮箱地址&#xff0c;这样在提交代码时就能知道是谁提交的。你可以在命令行中输入以下命令来配置&#xff1a; git config --global user.name "Your Name" git con…

政安晨:【Keras机器学习示例演绎】(十八)—— 图像字幕

目录 设置 下载数据集 准备数据 将文本数据向量化 构建用于训练的tf.data.Dataset管道 构建模型 模型训练 检查样本预测结果 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对…

ChuanhuChatGPT集成百川大模型

搭建步骤&#xff1a; 拷贝本地模型&#xff0c;把下载好的Baichuan2-7B-Chat拷贝到models目录下 修改modules\models\base_model.py文件&#xff0c;class ModelType增加Baichuan Baichuan 16 elif "baichuan" in model_name_lower: model_type ModelType.Ba…

8点法估计基础矩阵

估计基础矩阵 文章目录 估计基础矩阵8点法归一化 8点法 8点法 根据两幅图像中8个对应点对之间的关系&#xff0c;采用SVD求 解最小二乘方 约束&#xff1a;det(F) 0 假设已知N对点的对应关系&#xff1a; { x i , x i ′ } i 1 N \{x_i,x^{\prime}_i\}_{i1}^N {xi​,xi′​…

第一个大型汽车ITU-T车载语音通话质量实验室投入使用

中国汽车行业蓬勃发展&#xff0c;尤其是新能源汽车风起云涌&#xff0c;无论是国内还是海外需求旺盛的趋势下&#xff0c;除乘用车等紧凑型车外&#xff0c;中型汽车如MPV、小巴、小型物流车&#xff0c;大型汽车如重卡、泥头车等亦加入了手机互联、智驾的科技行列&#xff0c…

力扣题目:轮转数组

力扣题目&#xff1a;轮转数组 题目链接: 189.轮转数组 题目描述 代码思路 根据从轮转前到轮转后到数组变化&#xff0c;我们可以将数组元素分成两个部分&#xff0c;一个部分数轮转后从右边调到前面&#xff0c;一部分仅仅从左边向右移动。发现这个规律后&#xff0c;将数组…

软件工程的介绍

软件工程 这一章的内容其实还是蛮多的,大概一共有10个章节,分别是下面的一些内容,但是呢,这一章的内容其实是比较偏向文科类的,也就是说,记忆的内容其实占有很大的篇幅,在该考试科目当中呢,其实也是主要影响上午题部分的选择题的考察,基本的分值呢,在10分左右,分值占…

python自定义交叉熵损失,再和pytorch api对比

背景 我们知道&#xff0c;交叉熵本质上是两个概率分布之间差异的度量&#xff0c;公式如下 其中概率分布P是基准&#xff0c;我们知道H(P,Q)>0&#xff0c;那么H(P,Q)越小&#xff0c;说明Q约接近P。 损失函数本质上也是为了度量模型和完美模型的差异&#xff0c;因此可以…

input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案

文章目录 input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案问题描述解决办法 onCompositionStart与onCompositionEnd input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案 问题描述 测试环境&#xff1a;react antd input (react的事件与原生…

如何在TestNG中忽略测试用例

在这篇文章中&#xff0c;我们将讨论如何在TestNG中忽略测试用例。TestNG帮助我们忽略使用Test注释的情况&#xff0c;我们可以在不同的级别上忽略这些情况。 首先&#xff0c;只忽略一个测试方法或测试用例。第二&#xff0c;忽略一个类及其子类中的所有情况。第三个是&#…

QT中基于TCP的网络通信

QT中基于TCP的网络通信 QTcpServer公共成员函数信号 QTcpSocket公共成员函数信号 通信流程服务器端通信流程代码 客户端通信流程代码 使用Qt提供的类进行基于TCP的套接字通信需要用到两个类&#xff1a; QTcpServer&#xff1a;服务器类&#xff0c;用于监听客户端连接以及和客…

【算法基础实验】图论-UnionFind连通性检测之quick-find

Union-Find连通性检测之quick-find 理论基础 在图论和计算机科学中&#xff0c;Union-Find 或并查集是一种用于处理一组元素分成的多个不相交集合&#xff08;即连通分量&#xff09;的情况&#xff0c;并能快速回答这组元素中任意两个元素是否在同一集合中的问题。Union-Fin…

RDD编程初级实践

参考链接 spark入门实战系列--8MLlib spark 实战_mob6454cc68310b的技术博客_51CTO博客https://blog.51cto.com/u_16099212/7454034 Spark和Hadoop的安装-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/138021948?spm1001.2014.3001.5501 1. spark-shell…

Linux的学习之路:22、线程(2)

摘要 本章继续讲一下线程的东西 目录 摘要 一、抢票 二、加锁保护 三、死锁 1、死锁四个必要条件 2、避免死锁 四、同步 1、常见的线程安全的情况 2、常见不可重入的情况 3、常见可重入的情况 4、可重入与线程安全联系 5、可重入与线程安全区别 一、抢票 这里回…

大模型咨询培训老师叶梓:利用知识图谱和Llama-Index增强大模型应用

大模型&#xff08;LLMs&#xff09;在自然语言处理领域取得了显著成就&#xff0c;但它们有时会产生不准确或不一致的信息&#xff0c;这种现象被称为“幻觉”。为了提高LLMs的准确性和可靠性&#xff0c;可以借助外部知识源&#xff0c;如知识图谱。那么我们如何通过Llama-In…

clickhouse与oracle传输数据

参考 https://github.com/ClickHouse/clickhouse-jdbc-bridge https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/master/docker/README.md clickhouse官方提供了一种方式&#xff0c;可以实现clickhouse与oracle之间传输数据&#xff0c;不仅仅是oracle&#xff0…

Java后端利用百度地图全球逆地理编码,获取地址

声明&#xff1a;本人是在实习项目的时候遇到的问题 一.使用Api分为四步骤全球逆地理编码 rgc 反geo检索 | 百度地图API SDK 步骤1,2自行完成 接下来去获取AK 二.申请AK 登录百度账号 点击创建应用&#xff0c;选择自己想用的服务&#xff0c;我只单选了逆地理编码&#xff…

debian gnome-desktop GUI(图形用户界面)系统

目录 &#x1f31e;更新 &#x1f3a8;安装 &#x1f34e;分配 &#x1f6cb;️重启 &#x1f511;通过VNC连接 debian gnome-desktop &#x1f31e;更新 sudo apt update sudo apt -y upgrade &#x1f3a8;安装 sudo apt -y install task-gnome-desktop 这个过程比…

企业应该如何处理云安全问题。

企业甚至云提供商配置错误的云基础设施可能会导致多个漏洞&#xff0c;从而显着增加组织的攻击面。德迅云安全通过帮助企业和部署云安全的核心组件来解决这些问题。其中包括全面的安全态势、持续优化的策略、全周期风险管理、流量监控、威胁响应、风险缓解和数字资产管理。 云安…

力扣-有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff09; 注…