线程池
线程池的生命周期,总共有五种状态
- RUNNING :能接受新提交的任务,并且也能处理任务队列中的任务;
- SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
- STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
- TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
- TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
为什么要用线程池
- 降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
ThreadPoolExecutor线程池类参数详解
参数 |
说明
|
corePoolSize
|
核心线程数量,线程池维护线程的最少数量
|
maximumPoolSize
|
线程池维护线程的最大数量
|
keepAliveTime
|
线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁
|
unit
|
keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
|
workQueue
|
线程池所使用的任务缓冲队列
|
threadFactory
|
线程工厂,用于创建线程,可以规范线程名称
|
handler |
线程池对拒绝任务的处理策略
|
当线程池任务处理不过来的时候(什么时候认为处理不过来后面描述),可以通过handler指定的策略进行处理,ThreadPoolExecutor提供了四种策略:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。
核心线程数一般配置多少?
根据线程池执行的任务特性设置,有条件最好根据压测结果调整线程数量。
CPU密集型任务
-
CPU密集型任务也叫计算密集型任务。
-
比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。
-
CPU密集型任务最佳的线程数为 CPU 核心数的 1~2 倍。
-
如果设置过多的线程数,实际上并不会起到很好的效果。
-
假设我们设置的线程数量是 CPU 核心数的 2 倍以上,因为计算任务非常重,会占用大量的 CPU 资源,所以这时 CPU 的每个核心工作基本都是满负荷的。
-
我们又设置了过多的线程,每个线程都想去利用 CPU 资源来执行自己的任务,这就会造成不必要的上下文切换,此时线程数的增多并没有让性能提升,反而由于线程数量过多会导致性能下降。
IO密集型任务
-
比如数据库、文件的读写,网络通信等任务。
-
并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。
-
IO密集型任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。
-
如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在工作队列中等待的任务就会减少,可以更好地利用资源。
-
《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法:线程数 = CPU 核心数 *(1+线程执行任务时CPU等待时间/线程占有CPU工作时间)。通过这个公式,我们可以计算出一个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加,而如果平均工作时间长,也就是对于我们上面的 CPU 密集型任务,线程数就随之减少。
周期性线程池
/**
command参数是任务实例,
initialDelay参数是初始换延迟时间,
delay参数是延迟间隔时间,
unit参数是时间单元
**/
scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
- scheduleWithFixedDelay固定执行间隔时间,从执行结束计算间隔时间。
- scheduleAtRate固定执行频率,从执行开始计算间隔时间。
线程池任务执行
- submit() 该方法返回一个Future对象,可执行带返回值的线程;或者执行想随时可以取消的线程。Future对象的get()方法获取返回值。Future对象的cancel(true/false)取消任务,未开始或已完成返回false,参数表示是否中断执行中的线程。
- execute() 没有返回值。
线程池任务提交过程

- 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
- 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
- 当workQueue使用的是无界限队列时,maximumPoolSize参数就变的无意义了,比如new LinkedBlockingQueue(),或者new ArrayBlockingQueue(Integer.MAX_VALUE)。
- 使用SynchronousQueue队列时由于该队列没有容量的特性,所以不会对任务进行排队,如果线程池中没有空闲线程,会立即创建一个新线程来接收这个任务。maximumPoolSize要设置大一点。
- 核心线程和最大线程数量相等时keepAliveTime无作用。
执行源码
线程池关闭
- shutdown() 不接收新任务,会处理已添加任务
- shutdownNow() 不接受新任务,不处理已添加任务,中断正在处理的任务
常用队列介绍
- ArrayBlockingQueue: 这是一个由数组实现的容量固定的有界阻塞队列。
- SynchronousQueue: 没有容量,不能缓存数据;每个put必须等待一个take; offer()的时候如果没有另一个线程在poll()或者take()的话返回false。
- LinkedBlockingQueue: 这是一个由单链表实现的默认无界的阻塞队列。LinkedBlockingQueue提供了一个可选有界的构造函数,而在未指明容量时,容量默认为Integer.MAX_VALUE。
方法
|
说明
|
add
|
增加一个元索; 如果队列已满,则抛出一个异常
|
remove
|
移除并返回队列头部的元素; 如果队列为空,则抛出一个异常
|
offer
|
添加一个元素并返回true; 如果队列已满,则返回false
|
poll
|
移除并返回队列头部的元素; 如果队列为空,则返回null
|
put
|
添加一个元素; 如果队列满,则阻塞
|
take
|
移除并返回队列头部的元素; 如果队列为空,则阻塞
|
element
|
返回队列头部的元素; 如果队列为空,则抛出一个异常
|
peek
|
返回队列头部的元素; 如果队列为空,则返回null
|
Executors线程工厂类
- Executors.newCachedThreadPool();
- Executors.newFixedThreadPool(int)
说明: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
内部实现:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
- Executors.newSingleThreadExecutor();
- Executors.newScheduledThreadPool(int);
阿里巴巴Java开发手册中对线程池的使用规范
- 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例: public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); ... } }
- 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
- 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
优雅关闭
/* Iterates over all application hooks creating a new thread for each * to run in. Hooks are run concurrently and this method waits for * them to finish. */ static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; } for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { while (true) { try { hook.join(); break; } catch (InterruptedException ignored) { } } } } }
// com.google.common.util.concurrent.MoreExecutors#shutdownAndAwaitTermination public static boolean shutdownAndAwaitTermination(ExecutorService service, long timeout, TimeUnit unit) { // 把阻塞时间平分 long halfTimeoutNanos = unit.toNanos(timeout) / 2; // Disable new tasks from being submitted // 这一步是必做的,保证线程池不再接受新的任务 service.shutdown(); try { // Wait for half the duration of the timeout for existing tasks to terminate // 先最多等设置的阻塞时间的一半时间 if (!service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS)) { // Cancel currently executing tasks // 考虑到有些任务可能处于阻塞状态,直接 shutdownNow,会中断执行的任务 service.shutdownNow(); // Wait the other half of the timeout for tasks to respond to being cancelled service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS); } } catch (InterruptedException ie) { // Preserve interrupt status Thread.currentThread().interrupt(); // (Re-)Cancel if current thread also interrupted service.shutdownNow(); } return service.isTerminated(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?