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

news2024/11/26 8:21:46

线程池的基本概念

线程池是一种基于池化技术的线程使用方式,它允许我们有效地管理和复用线程,减少线程的创建和销毁的开销,从而提高系统的响应速度。在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并发编程中,通过精细调整线程池的各项参数,可以有效地管理线程生命周期,减少线程创建和销毁的开销,从而加快系统响应速度。

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

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

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

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

相关文章

docker可视化

什么是portainer&#xff1f; portainer就是docker图形化界面的管理工具&#xff0c;提供一个后台面板供我们操作 目前先用portainer(先用这个)&#xff0c;以后还会用到Rancher(CI/CD在用) 1.下载portainer 9000是内网端口&#xff0c;8088是外网访问端口 docker run…

19 数据中心详解

1、数据中心的概念 其实平时我们不管是看新闻&#xff0c;视频&#xff0c;下载文件等&#xff0c;最终访问的目的地都是在数据中心里面。数据中心存放的是服务器&#xff0c;区别于我们平时使用的笔记本或者台式机。 机架&#xff1a;数据中心的服务器被放在一个个叫作机架&…

Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍

Go 接口&#xff1a;Go中最强大的魔法,接口应用模式或惯例介绍 文章目录 Go 接口&#xff1a;Go中最强大的魔法,接口应用模式或惯例介绍一、前置原则二、一切皆组合2.1 一切皆组合2.2 垂直组合2.2.1 第一种&#xff1a;通过嵌入接口构建接口2.2.2 第二种&#xff1a;通过嵌入接…

Guli商城-商品服务-API-三级分类-配置网关路由与路径重写

启动人人fast服务&#xff1a; 打开本地的前端项目&#xff0c;启动&#xff1a; 命令&#xff1a;npm run dev 账号密码&#xff1a;admin/admin 对应的数据库&#xff1a; 接下来在商品系统目录中添加子菜单&#xff1a; 数据库中可以看到记录 退出账号&#xff0c;重新登录…

vue中实现千位分隔符

vue中实现千位分隔符有两种&#xff0c;一种是某一个字段转换&#xff0c;一种是表格table中的整列字段转换 比如将3236634.12&#xff0c;经过转换后变为 3,236,634.12 1. 某一个字段转换 写js方法&#xff1a; export function numberExchange(value){if (!value) return…

Android自定义 View惯性滚动效果(不使用Scroller)

效果图&#xff1a; 前言&#xff1a; 看了网上很多惯性滚动方案&#xff0c;都是通过Scroller 配合 computeScroll实现的&#xff0c;但在实际开发中可能有一些场景不合适&#xff0c;比如协调布局&#xff0c;内部子View有特别复杂的联动效果&#xff0c;需要通过偏移来配合…

JavaScript从入门到精通系列第三十二篇:详解正则表达式语法(一)

文章目录 一&#xff1a;正则表达式 1&#xff1a;量词设置次数 2&#xff1a;检查字符串以什么开头 3&#xff1a;检查字符串以什么结尾 4&#xff1a; 同时使用开头结尾 5&#xff1a;同值开头同值结尾 二&#xff1a;练习 1&#xff1a;检查是否是一个手机号 大神链…

(附源码)基于Springboot智慧园区管理系统-计算机毕设 88160

Springboot智慧园区管理系统的开发 摘要 随着互联网趋势的到来&#xff0c;互联网概念越来越盛行&#xff0c;园区管理最好方式就是建立自己的互联网系统。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Springboot框架建设智慧园区管理系统。 本设计主…

线性代数(五) | 矩阵对角化 特征值 特征向量

文章目录 1 矩阵的特征值和特征向量究竟是什么&#xff1f;2 求特征值和特征向量3 特征值和特征向量的应用4 矩阵的对角化 1 矩阵的特征值和特征向量究竟是什么&#xff1f; 矩阵实际上是一种变换,是一种旋转伸缩变换&#xff08;方阵&#xff09; 不是方阵的话还有可能是一种…

博阳精讯、凡得科技访问上海斯歌:共探BPM流程服务新高地

10月27日下午&#xff0c;来自博阳精讯、凡得科技的流程领域专家、领导一行参观访问了上海斯歌总部。三方举行了深度交流会谈&#xff0c;分享了彼此对流程领域的前沿洞察和技术实践&#xff0c;共同探索了BPM流程服务科技力与价值力的新高地。 本次研讨会上&#xff0c;博阳精…

WPF 线程模型

Windows Presentation Foundation (WPF) 旨在将开发人员从线程处理困难中解脱出来。 因此&#xff0c;大多数 WPF 开发人员不会编写使用多个线程的界面。 由于多线程程序既复杂又难以调试&#xff0c;因此当存在单线程解决方案时&#xff0c;应避免使用多线程程序。 但是&…

【Docker安装RockeMQ:基于Windows宿主机,并重点解决docker rocketMQ安装情况下控制台无法访问的问题】

拉取镜像 docker pull rocketmqinc/rocketmq创建网络 docker network create rocketmq-net构建namesrv容器 docker run -d -p 9876:9876 -v D:/dockerFile/rocketmq/namesrv/logs:/root/logs -v D:/dockerFile/rocketmq/namesrv/store:/root/store --network rocketmq-net -…

11.9存储器实验总结(单ram,双ram,FIFO)

实验设计 单端口RAM实现 双端口RAM实现 FIFO实现 文件结构为

【Royalty in Wind 2.0.0】个人体测计算、资料分享小程序

前言 Royalty in Wind 是我个人制作的一个工具类小程序。主要涵盖体测计算器、个人学习资料分享等功能。这个小程序在2022年第一次发布&#xff0c;不过后来因为一些原因暂时搁置。现在准备作为我个人的小程序重新投入使用XD PS&#xff1a;小程序开发部分我是在21年跟随郄培…

使命担当 守护安全 | 中睿天下获全国海关信息中心感谢信

近日&#xff0c;全国海关信息中心向中睿天下发来感谢信&#xff0c;对中睿天下在2023年网络攻防演练专项活动中的大力支持和优异表现给予了高度赞扬。 中睿天下对此次任务高度重视&#xff0c;紧密围绕全国海关信息中心的行动要求&#xff0c;发挥自身优势有效整合资源&#x…

Spring Boot 3.0正式发布及新特性解读

目录 【1】Spring Boot 3.0正式发布及新特性依赖调整升级的关键变更支持 GraalVM 原生镜像 Spring Boot 最新支持版本Spring Boo 版本版本 3.1.5前置系统清单三方包升级 Ref 个人主页: 【⭐️个人主页】 需要您的【&#x1f496; 点赞关注】支持 &#x1f4af; 【1】Spring Boo…

GUI:贪吃蛇

以上是准备工作 Data import javax.swing.*; import java.net.URL;public class Data {public static URL headerURLData.class.getResource("static/header.png");public static ImageIcon header new ImageIcon(headerURL);public static URL upURLData.class.getR…

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

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

如何设计vue项目的权限管理?

权限管理的重要性及必要性 数据安全&#xff1a;权限管理可以确保只有具有相应权限的用户能够访问和操作特定的数据。这可以保护敏感数据不被未授权的用户访问&#xff0c;从而提高数据的安全性。功能控制&#xff1a;权限管理可以根据用户的角色和权限设置&#xff0c;控制用户…

Ansible自动化运维工具(常用模块与命令)

ansible基于Python开发&#xff0c;实现了批量系统配置&#xff0c;批量程序部署&#xff0c;批量运行命令等功能 ansible特点 部署简单&#xff0c;只需在主控端部署Ansible环境&#xff0c;被控端无需做任何操作&#xff1b;默认使用ssh协议对设备进行管理&#xff1b;有大…