Java线程池的几个常见问题
1. Java自带的线程池?有哪些实现?
Java通过Executors
工厂类提供了几种快速创建线程池的便捷方法。这些方法内部都是通过ThreadPoolExecutor
或ForkJoinPool
的不同参数配置来实现的。
主要实现有:
newFixedThreadPool(int nThreads)
- 特点:创建固定大小的线程池。核心线程数 = 最大线程数 =
nThreads
。使用无界的LinkedBlockingQueue
作为工作队列。 - 适用场景:适用于为了满足资源管理的需求,需要限制当前线程数量的场景,适用于负载较重的服务器。
- 特点:创建固定大小的线程池。核心线程数 = 最大线程数 =
newCachedThreadPool()
- 特点:创建一个可缓存的线程池。核心线程数为0,最大线程数为
Integer.MAX_VALUE
。使用同步移交队列SynchronousQueue
。空闲线程存活时间为60秒。 - 适用场景:适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。会根据任务量弹性地创建和回收线程。
- 特点:创建一个可缓存的线程池。核心线程数为0,最大线程数为
newSingleThreadExecutor()
- 特点:创建只有一个线程的线程池。核心线程数 = 最大线程数 = 1。使用无界的
LinkedBlockingQueue
。 - 适用场景:适用于需要保证任务顺序执行(FIFO, LIFO, 优先级)的场景。
- 特点:创建只有一个线程的线程池。核心线程数 = 最大线程数 = 1。使用无界的
newScheduledThreadPool(int corePoolSize)
- 特点:创建一个固定大小的线程池,支持定时及周期性任务执行。内部实现是
ScheduledThreadPoolExecutor
。 - 适用场景:需要执行延迟任务或周期性任务的场景。
- 特点:创建一个固定大小的线程池,支持定时及周期性任务执行。内部实现是
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
,其执行流程是一个非常经典的状态机流程,如下图所示:
A[提交新任务 execute(runnable)] --> B{核心线程数已满?}
B -- 否 --> C[创建新的核心线程执行任务]
B -- 是 --> D{任务队列已满?}
D -- 否 --> E[将任务加入工作队列]
D -- 是 --> F{最大线程数已满?}
F -- 否 --> G[创建新的临时线程执行任务]
F -- 是 --> H[执行拒绝策略]
E --> I[等待核心线程空闲]
I --> J[从队列头部取出任务执行]
- 当一个新任务被提交时,线程池首先检查当前运行的线程数是否小于核心线程数(corePoolSize)。如果小于,则立即创建一个新的核心线程来执行该任务(即使其他核心线程是空闲的)。
- 如果当前运行的线程数已经达到或超过核心线程数,线程池会将任务尝试放入工作队列(BlockingQueue) 进行缓冲等待。
- 如果工作队列已满,线程池会检查当前运行的线程数是否小于最大线程数(maximumPoolSize)。如果小于,则会创建新的“非核心”线程来立即执行这个新提交的任务(而不是处理队列里的旧任务)。
- 如果当前线程数已经达到最大线程数,并且队列也已满,那么说明线程池已经饱和,无法处理新任务。此时会触发拒绝策略(RejectedExecutionHandler) 来处理这个被拒绝的任务。
核心原则:先扩核心线程 -> 再入队列 -> 再扩临时线程 -> 最后拒绝。
5. 拒绝策略有哪些?
当线程池和队列都饱和时,会执行拒绝策略。JDK内置了4种策略,都实现了RejectedExecutionHandler
接口:
ThreadPoolExecutor.AbortPolicy
(默认策略)- 行为:直接抛出
RejectedExecutionException
异常。 - 适用场景:这是最严格的策略,可以确保任务不会丢失,提交任务的调用方可以捕获异常并做相应处理。
- 行为:直接抛出
ThreadPoolExecutor.CallerRunsPolicy
- 行为:不会丢弃任务,也不会抛出异常。而是将任务回退给调用者线程来执行。即谁(哪个线程)提交的任务,就由哪个线程自己来执行。
- 适用场景:这是一种负反馈机制,可以有效地降低新任务提交的速度,给线程池喘息的时间。如果任务提交方是Web服务器的处理线程,那么服务器线程将忙于执行被拒绝的任务,从而无法继续提交新任务,起到了平缓流量的作用。
ThreadPoolExecutor.DiscardPolicy
- 行为:静默地直接丢弃无法被处理的新任务,不做任何通知,就像这个任务从来没被提交过一样。
- 适用场景:允许任务丢失的场景,不关键。
ThreadPoolExecutor.DiscardOldestPolicy
- 行为:丢弃工作队列头部(即下一个即将被执行的)最老的任务,然后尝试重新提交当前这个新任务。
- 适用场景:允许丢弃老任务,希望新任务有机会被执行的场景。需要注意队列中任务的优先级,防止重要的老任务被丢弃。
除了使用内置策略,你也可以实现RejectedExecutionHandler
接口,自定义拒绝策略,例如将拒绝的任务持久化到磁盘或记录日志等待后续重试。
点赞 | 关注 | 评论 —— 你的每一次互动,都是笔者持续创作的最大动力!
想了解更多技术深度解析?点击关注按钮,订阅更新不迷路!
Java线程池的几个常见问题的更多相关文章
- Java线程池的几种实现 及 常见问题讲解
工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java线程池使用说明
Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...
- (转载)JAVA线程池管理
平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- Java线程池应用
Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- Java线程池与java.util.concurrent
Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...
- [转 ]-- Java线程池使用说明
Java线程池使用说明 原文地址:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1. ...
随机推荐
- 解决Dev c++6版本中文输出乱码问题
解决Dev c++6.3版本中文输出乱码问题 1.遇到的问题 使用cout输出中文时出现乱码问题 如下图所示: 2.解决方法 找到[工具]--[编译选项]--勾选[编译时加入以下命令]并添加如下代码: ...
- HyperMesh打开保存文件与面板功能使用
打开和保存文件 在 HyperMesh 中,打开和保存文件是最常用的命令.本节介绍 HyperMesh 打开和保存文件的多种方式.后续的练习中假定用户已经熟练使用 HyperMesh 进行文件打开和保 ...
- 让Smarty模板生成html文件
用Smarty模板生成html文件,其实就是在 display() 的同时,增加了 ob_start().b_get_contents() 和 fwrite() 函数. 具体实现方法,请看下面两个文件 ...
- 智能手机无音频场景使用时Audio DSP低功耗的处理
智能手机(或智能手表)等用电池的电子设备对功耗比较敏感,因此不管是使用中还是待机时都要做低功耗处理来省电.前面的文章(智能手表音乐播放功耗的优化)讲了一款智能手表在播放音乐时的低功耗优化,这属于音频场 ...
- JS循环遍历对象,获取key:value
https://blog.csdn.net/lyn1772671980/article/details/79093459 let obj = { 'a':'aa', 'b' ...
- 设置div 内容不会换行 显示滚动条
overflow-x: scroll; display: inline-block; white-space: nowrap; 内容不能脱离文档流否则还是会把div 撑开
- AtCoder Beginner Contest 187 ABCDE 题解
A - Large Digits 思路:签到题,读入字符串即可. view code #include<iostream> #include<string> #include& ...
- leetcode 443 压缩字符串
简介 常规思路, 看了官方的. code class Solution { public int compress(char[] chars) { int anchor = 0, write = 0; ...
- MySQL 16“order by”是怎么工作的?
假设要查询城市是"杭州"的所有人名字,并且按照姓名排序返回前1000个人的姓名与年龄.那么SQL语句可以写为: select city,name,age from t where ...
- Counting Principles计数原理•Arrangement排列•Combination组合•Binomial Theorem二项式定理
Sampling K objects out of N : Does the K samples have order? is the object replaceable? Labeling: la ...