面试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, // corePoolSize
            4, // maximumPoolSize
            60L, // keepAliveTime
            TimeUnit.SECONDS, // unit
            new LinkedBlockingQueue<Runnable>(1024), // workQueue
            Executors.defaultThreadFactory(), // threadFactory
            new ThreadPoolExecutor.CallerRunsPolicy() // handler
        );

        executor.execute(new Runnable() {
            @Override
            public 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, // corePoolSize
    4, // maximumPoolSize
    60, // keepAliveTime
    TimeUnit.SECONDS, // unit
    new LinkedBlockingQueue<>(1024), // workQueue
    Executors.defaultThreadFactory(), // threadFactory
    new ThreadPoolExecutor.AbortPolicy() // handler
);

// 提交任务到线程池
executor.execute(() -> {
    System.out.println("任务执行");
});

// 关闭线程池
executor.shutdown();

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

总结

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python】AppUI自动化—appium自动化开发环境部署、APP测试案例(17)上

文章目录 一.appium简介1.什么是appium2.appium 的工作原理3.APP类型4.APP页面布局 二,appium开发环境部署&#xff08;python环境&#xff09;1.下载安装环境1.1.下载安装所需环境1.2.Appium-desktop&#xff08; Appium-Server-GUI &#xff09;配置1.3.Appium-Inspector 配置…

Python堆栈详细介绍

概要 虽然一些数据结构是通用的并且可以在广泛的应用中使用&#xff0c;但其他数据结构是专门化的并且被设计用于处理特定问题。堆栈就是这样一种专门的结构&#xff0c;以其简单性和非凡的实用性而闻名。 那么&#xff0c;什么是栈呢&#xff1f;从本质上讲&#xff0c;堆栈…

web3通过antd 在React dapp中构建订单组件基本结构

上文web3 dapp React项目引入 antd 对 balance 用户token信息组件进行样式改造 中 我们导入 antd组件 算是比较完整的编写了用户资产组件 那么 今天开始 我们就要说订单组件了 这个就会比之前的复杂很多 我们还是先开环境 ganache 终端执行 ganache -d然后 将合约 发布到区块链…

使用promise创建一个同步事件驱动api

使用promise创建一个同步事件驱动api 事件驱动体系结构(EDA)是一种强大的方法&#xff0c;可以在网络上构建松散耦合、性能好、可伸缩的应用程序。它支持非常多功能&#xff0c;如推送通知、协作编辑和多人操作&#xff0c;以及其他实时交互。 但有时模型与我们开发人员需要的…

【树的存储结构,孩子链表】

文章目录 树和森林树的存储结构孩子链表 树和森林 森林&#xff1a;是m(m>0)棵互不相交的树的集合。 树的存储结构 1.双亲表示法 实现&#xff1a;定义结构数组存放树的结点&#xff0c;每个结点含两个域。 数据域&#xff1a;存放结点本身信息。 双亲域&#xff1a;指…

虚假内容检测,谣言检测,不实信息检测,事实核查;纯文本,多模态,多语言;数据集整理

本博客系博主个人理解和整理所得&#xff0c;包含内容无法详尽&#xff0c;如有补充&#xff0c;欢迎讨论。 这里只提供数据集相关介绍和来源出处&#xff0c;或者下载地址等&#xff0c;因版权原因不提供数据集所含的元数据。如有需要&#xff0c;请自行下载。 “Complete d…

亚马逊云科技海外服务器初体验

目录 前言亚马逊云科技海外服务器概述注册使用流程实例创建性能表现用户体验服务支持初体验总结 前言 随着云原生技术的飞速发展&#xff0c;越来越多的企业和开发者选择云服务器来作为自己的使用工具&#xff0c;云原生技术的发展也促进了云服务厂商的产品发展&#xff0c;所…

Leetcode Hot 100之四:283. 移动零+11. 盛最多水的容器

283.移动零 题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] …

算法通关村第七关-黄金挑战二叉树迭代遍历

大家好我是苏麟 , 今天带来二叉树的迭代遍历 . 二叉树的迭代遍历 前序编列 描述 : 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 题目 : LeetCode 二叉树的前序遍历 : 144. 二叉树的前序遍历 分析 : 前序遍历是中左右&#xff0c;如果还有左子树就一…

一款基于.Net开发、开源、支持多平台云存储文件管理器

目录 01 项目简介02 项目代码03 部分截图04 项目地址 今天给大家推荐一款基于基于.Net开发、开源的&#xff0c;支持多平台的云存储文件管理器。 01 项目简介 Camelotia是一款云存储文件管理器&#xff0c;基于.Net UI框架和ReactiveUI框架开发的&#xff0c;目前支持的平台有…

AI:75-基于生成对抗网络的虚拟现实场景增强

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

Bitget Wallet:使用 Base 链购买 ETH 的简明教程

Base 链是一种 Layer 2&#xff08;L2&#xff09;公链&#xff0c;它可以为用户提供以太坊&#xff08;ETH&#xff09;代币&#xff0c;而 Bitget Wallet 是一款多功能加密货币钱包&#xff0c;支持 Base 链以及其他主要区块链。

进行 “最佳价格查询器” 的开发

前置条件 public class Shop {private final String name;private final Random random;public Shop(String name) {this.name name;random new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));}public double getPrice(String product) {return calculatePrice…

SpringBoot自动配置的原理篇,剖析自动配置原理;实现自定义启动类!附有代码及截图详细讲解

SpringBoot 自动配置 Condition Condition 是在Spring 4.0 增加的条件判断功能&#xff0c;通过这个可以功能可以实现选择性的创建 Bean 操作 思考&#xff1a;SpringBoot是如何知道要创建哪个Bean的&#xff1f;比如SpringBoot是如何知道要创建RedisTemplate的&#xff1f;…

剖析WPF模板机制的内部实现

剖析WPF模板机制的内部实现 众所周知&#xff0c;在WPF框架中&#xff0c;Visual类是可以提供渲染&#xff08;render&#xff09;支持的最顶层的类&#xff0c;所有可视化元素&#xff08;包括UIElement、FrameworkElment、Control等&#xff09;都直接或间接继承自Visual类。…

单例模式 rust和java的实现

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

【第2章 Node.js基础】2.4 Node.js 全局对象...持续更新

什么是Node.js 全局对象 对于浏览器引擎来说&#xff0c;JavaScript 脚本中的 window 是全局对象&#xff0c;而Node.js程序中的全局对象是 global&#xff0c;所有全局变量(除global本身外)都是global 对象的属性。全局变量和全局对象是所有模块都可以调用的。Node.is 的全局…

docker部署mongodb

1&#xff1a;拉去momgodb镜像 2&#xff1a;拉去成功后&#xff0c;通过docker-compose.yml配置文件启动mongodb&#xff0c;docker-compose.yml配置如下 version: 3.8 services:mongodb-1:container_name: mongodbimage: mongo ports:- "27017:27017"volumes:- G:…