当前位置: 首页 > ds >正文

Java线程池的几个常见问题

1. Java自带的线程池?有哪些实现?

Java通过Executors工厂类提供了几种快速创建线程池的便捷方法。这些方法内部都是通过ThreadPoolExecutorForkJoinPool的不同参数配置来实现的。

主要实现有:

1、newFixedThreadPool(int nThreads)

  • 特点:创建固定大小的线程池。核心线程数 = 最大线程数 = nThreads。使用无界的LinkedBlockingQueue作为工作队列。

  • 适用场景:适用于为了满足资源管理的需求,需要限制当前线程数量的场景,适用于负载较重的服务器。

2、newCachedThreadPool()

  • 特点:创建一个可缓存的线程池。核心线程数为0,最大线程数为Integer.MAX_VALUE。使用同步移交队列SynchronousQueue。空闲线程存活时间为60秒。

  • 适用场景:适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。会根据任务量弹性地创建和回收线程。

3、newSingleThreadExecutor()

  • 特点:创建只有一个线程的线程池。核心线程数 = 最大线程数 = 1。使用无界的LinkedBlockingQueue

  • 适用场景:适用于需要保证任务顺序执行(FIFO, LIFO, 优先级)的场景。

4、newScheduledThreadPool(int corePoolSize)

  • 特点:创建一个固定大小的线程池,支持定时及周期性任务执行。内部实现是ScheduledThreadPoolExecutor

  • 适用场景:需要执行延迟任务或周期性任务的场景。

5、newWorkStealingPool(int parallelism) (JDK 1.8+)

  • 特点:创建一种窃取工作的线程池。使用ForkJoinPool实现,并行度默认为CPU核心数。使用工作窃取算法,可以充分利用多核CPU的性能。

  • 适用场景:非常适合会产生子任务的计算密集型任务。


2. 为什么不用默认实现?

虽然Executors提供的工厂方法很方便,但在生产环境中不推荐直接使用,原因如下:

  • newFixedThreadPool 和 newSingleThreadExecutor:它们使用的 workQueue 是默认大小为 Integer.MAX_VALUE 的 LinkedBlockingQueue(无界队列)。如果任务提交速度持续远大于任务处理速度,会导致大量任务堆积在队列中,最终耗尽内存,引发 OutOfMemoryError

  • newCachedThreadPool:它允许创建的线程数量为 Integer.MAX_VALUE。如果任务数量非常多且执行时间较长,可能会导致创建大量的线程,耗尽CPU和内存资源。

核心问题:这些方法的参数是固化的,缺乏自定义性,容易导致资源耗尽的风险。

最佳实践:根据实际的业务场景(任务类型、数量、峰值等),手动直接创建 ThreadPoolExecutor 实例,以便清晰地指定核心线程数、最大线程数、队列类型和容量、拒绝策略等参数,从而做出更精细和安全的资源配置。


3. 线程池中的线程是IO密集型还是计算密集型?

线程池本身不区分IO密集型还是计算密集型。池中的线程只是“工人”,它们是什么类型,完全取决于你提交给它们的“任务(Runnable/Callable)”是什么类型

  • 如果你提交的任务是大量的网络请求、数据库操作、文件读写等,需要等待IO响应的,那么这些线程就是在执行IO密集型任务。

  • 如果你提交的任务是大量的复杂算法、循环计算、数据处理等,主要消耗CPU资源的,那么这些线程就是在执行计算密集型任务。

区分的重要性在于如何设置线程池参数

  • 计算密集型:线程数通常设置为 CPU核心数 + 1 左右。过多线程会导致频繁的CPU上下文切换,反而降低性能。

  • IO密集型:由于线程在执行IO操作时会阻塞,CPU空闲,因此可以设置更多的线程数,以充分利用CPU资源。通常可以设置为 CPU核心数 * (1 + 平均IO等待时间 / 平均CPU计算时间),这个公式的估算值可能是 2 * CPU核心数 或更高,需要通过压测找到最佳值。


4. 线程池执行任务的流程?

假设我们有一个自定义的 ThreadPoolExecutor,其执行流程是一个非常经典的状态机流程,如下图所示:

  1. 当一个新任务被提交时,线程池首先检查当前运行的线程数是否小于核心线程数(corePoolSize)。如果小于,则立即创建一个新的核心线程来执行该任务(即使其他核心线程是空闲的)。

  2. 如果当前运行的线程数已经达到或超过核心线程数,线程池会将任务尝试放入工作队列(BlockingQueue) 进行缓冲等待。

  3. 如果工作队列已满,线程池会检查当前运行的线程数是否小于最大线程数(maximumPoolSize)。如果小于,则会创建新的“非核心”线程来立即执行这个新提交的任务(而不是处理队列里的旧任务)。

  4. 如果当前线程数已经达到最大线程数,并且队列也已满,那么说明线程池已经饱和,无法处理新任务。此时会触发拒绝策略(RejectedExecutionHandler) 来处理这个被拒绝的任务。

核心原则:先扩核心线程 -> 再入队列 -> 再扩临时线程 -> 最后拒绝。


5. 拒绝策略有哪些?

当线程池和队列都饱和时,会执行拒绝策略。JDK内置了4种策略,都实现了RejectedExecutionHandler接口:

1、ThreadPoolExecutor.AbortPolicy(默认策略)

  • 行为:直接抛出RejectedExecutionException异常。

  • 适用场景:这是最严格的策略,可以确保任务不会丢失,提交任务的调用方可以捕获异常并做相应处理。

2、ThreadPoolExecutor.CallerRunsPolicy

  • 行为:不会丢弃任务,也不会抛出异常。而是将任务回退给调用者线程来执行。即谁(哪个线程)提交的任务,就由哪个线程自己来执行。

  • 适用场景:这是一种负反馈机制,可以有效地降低新任务提交的速度,给线程池喘息的时间。如果任务提交方是Web服务器的处理线程,那么服务器线程将忙于执行被拒绝的任务,从而无法继续提交新任务,起到了平缓流量的作用。

3、ThreadPoolExecutor.DiscardPolicy

  • 行为:静默地直接丢弃无法被处理的新任务,不做任何通知,就像这个任务从来没被提交过一样。

  • 适用场景:允许任务丢失的场景,不关键。

4、ThreadPoolExecutor.DiscardOldestPolicy

  • 行为:丢弃工作队列头部(即下一个即将被执行的)最老的任务,然后尝试重新提交当前这个新任务。

  • 适用场景:允许丢弃老任务,希望新任务有机会被执行的场景。需要注意队列中任务的优先级,防止重要的老任务被丢弃。

除了使用内置策略,你也可以实现RejectedExecutionHandler接口,自定义拒绝策略,例如将拒绝的任务持久化到磁盘或记录日志等待后续重试。

文章转载自:佛祖让我来巡山

原文链接:Java线程池的几个常见问题 - 佛祖让我来巡山 - 博客园

体验地址:JNPF快速开发平台

http://www.xdnf.cn/news/20374.html

相关文章:

  • 会员体系搭建咋做?定位目标人群竟有这么多讲究
  • GJOI 9.4 题解
  • Qt---JSON处理体系
  • LeetCode_位运算
  • 安卓学习 之 EditText 控件
  • C/C++中的可变参数 (Variadic Arguments)函数机制
  • Linux学习-硬件(串口通信)
  • 【Android】SQLite使用——增删查改
  • 有哪些AI产品可以真正提高办公和学习效率?
  • 【LeetCode】2749. 得到整数零需要执行的最少操作数
  • 关于无法导入父路径的问题
  • MySQL源码部署(rhel7)
  • SQL面试题及详细答案150道(61-80) --- 多表连接查询篇
  • java面试中经常会问到的集合问题有哪些(基础版)
  • GigaDevice(兆易创新)GD25Q64CSJGR 64Mbit FLASH
  • c#动态树形表达式详解
  • uni-app 和 uni-app x 的区别
  • 【Cell Systems】SpotGF空间转录组去噪算法文献分享
  • 图像去雾:从暗通道先验到可学习融合——一份可跑的 PyTorch 教程
  • <video> 标签基础用法
  • MySQL-安装MySQL
  • UE4 Mac构建编译报错 no template named “is_void_v” in namespace “std”
  • 无需bootloader,BootROM -> Linux Kernel 启动模式
  • Java全栈开发工程师面试实录:从基础到实战的深度探讨
  • PyTorch图像数据转换为张量(Tensor)并进行归一化的标准操作
  • 管理中心理学问:动机与管理的关联
  • 什么是CRM?定义、作用、功能、选型|CRM百科
  • 使用若依加Trae快速搭建一对儿多对多CRUD
  • 移植Qt4.8.7到ARM40-A5
  • PiscCode基于 Mediapipe 实现轨迹跟踪