线程池
1.什么是线程池?
可以以 new Thread( () -> { 线程执行的任务 }).start(); 这种形式开 启一个线程. 当 run()方法运行结束,线程对象会被 GC释放.
在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时 ,反而会耗尽 CPU 资源. 如果不对线程进行控制与管理, 反而会影响程序的性能.
线程开销主要包括: 创建与启动线程的开销; 线程销毁开销; 线程调度的开销; 线程数量受限 CPU 处理器数量.
线程池就是有效使用线程的一种常用方式. 线程池内部可以预先 创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交 给线程池, 线程池将这些任务缓存在工作队列中, 线程池中的工作线 程不断地从队列中取出任务并执行.
2.Executor 框架

3.线程池的底层实现
Executors 工 具 类 中 newCachedThreadPool(), newSingleThreadExcecutor(), newFixedThreadPool()源码:
一:newCachedThreadPool()可缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
该线程池在极端情况下,每次提交新的任务都会创建新的线程执行. 适合用来执行大量 耗时短并且提交频繁的任务
二:newFixedThreadPool() 定长线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
适用:执行长期的任务,性能好很多
三:newSingleThreadExecutor()单线程化线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
适用:一个任务一个任务执行的场景
四:newScheduledThreadPool() 周期性线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory){
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);}
workQueue为:new DelayedWorkQueue() 。一个按超时时间升序排序的队列
创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构.
适用:周期性执行任务的场景
....................................................................................................................................................................
Excutors 工 具 类 中 返 回 线 程 池 的 方 法 底 层 都 使 用 了 ThreadPoolExecutor 线程池,这些方法都是 ThreadPoolExecutor 线程池 的封装.
ThreadPoolExecutor 的构造方法: public ThreadPoolExecutor
(int corePoolSize,---------------- 指定线程池中核心线程的数量
int maximumPoolSize, -------- 指定线程池中最大线程数量
long keepAliveTime, ---------- 当线程池线程的数量超过 corePoolSize 时,多余的空 闲线程的存活时长,即空闲线程在多长时长内销毁
TimeUnit unit, -------------------- 是 keepAliveTime 时长单位
BlockingQueue
ThreadFactory threadFactory, ------ 线程工厂,用于创建线程
RejectedExecutionHandler handler) --- 拒绝策略,当任务太多来不及处理时,如何拒绝
............................................................................................................................................................
说明:
workQueue 工 作 队 列 是 指 提 交 未 执 行 的 任 务 队 列 , 它 是 BlockingQueue 接口的对象,仅用于存储 Runnable 任务.根据队列功 能分类,在 ThreadPoolExecutor 构造方法中可以使用以下几种阻塞 队列:
1)) 直接提交队列,由 SynchronousQueue 对象提供,该队列没有 容量,提交给线程池的任务不会被真实的保存,总是将新的任务提 交给线程执行,如果没有空闲线程,则尝试创建新的线程,如果线程 数量已经达到 maxinumPoolSize 规定的最大值则执行拒绝策略.
- )有 界 任 务 队 列 , 由 ArrayBlockingQueue 实 现 , 在 创 建 ArrayBlockingQueue 对象时,可以指定一个容量. 当有任务需要执 行时,如果线程池中线程数小于 corePoolSize 核心线程数则创建新 的线程;如果大于 corePoolSize 核心线程数则加入等待队列.如果队 列已满则无法加入,在线程数小于 maxinumPoolSize 指定的最大线 程 数 前 提 下 会 创 建 新 的 线 程 来 执 行 , 如 果 线 程 数 大 于 maxinumPoolSize 最大线程数则执行拒绝策略
![]()
3 ) 无界任务队列,由 LinkedBlockingQueue 对象实现,与有界队 列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的 情况. 当有新的任务时,在系统线程数小于 corePoolSize 核心线程 数则创建新的线程来执行任务;当线程池中线程数量大于corePoolSize 核心线程数则把任务加入阻塞队列
- )优先任务队列是通过 PriorityBlockingQueue 实现的,是带有 任 务 优 先 级 的 队 列 , 是 一 个 特 殊 的 无 界 队 列 . 不 管 是 ArrayBlockingQueue 队列还是 LinkedBlockingQueue 队列都是按照 先进先出算法处理任务的.在 PriorityBlockingQueue 队列中可以根 据任务优先级顺序先后执行.
4.拒绝策略
ThreadPoolExecutor 构造方法的最后一个参数指定了拒绝策略.当 提交给线程池的任务量超过实际承载能力时,如何处理?
即线程池中 的线程已经用完了,等待队列也满了,无法为新提交的任务服务,可以 通过拒绝策略来处理这个问题. JDK 提供了四种拒绝策略:
AbortPolicy 策略,会抛出异常
CallerRunsPolicy 策略,只要线程池没关闭,会在调用者线程中运行 当前被丢弃的任务
DiscardOldestPolicy 将任务队列中最老的任务丢弃,尝试再次提交 新任务
DiscardPolicy 直接丢弃这个无法处理的任务
Executors 工具类提供的静态方法返回的线程池默认的拒绝策略是 AbortPolicy 抛出异常,如果内置的拒绝策略无法满足实际需求,可以扩展 RejectedExecutionHandler 接口
5.线程池
线程池中的线程来自ThreadFactory.
ThreadFactory 是一个接口,只有一个用来创建线程的方法: Thread newThread(Runnable r);
- 监控线程池的方法:
ThreadPoolExecutor 提供了一组方法用于监控线程池
int getActiveCount() 获得线程池中当前活动线程的数量
long getCompletedTaskCount() 返回线程池完成任务的数量
int getCorePoolSize() 线程池中核心线程的数量
int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
int getMaximumPoolSize() 返回线程池的最大容量
int getPoolSize() 当前线程池的大小
BlockingQueue
long getTaskCount() 返回线程池收到的任务总数
- 扩展线程池:
有时需要对线程池进行扩展,如在监控每个任务的开始和结束时 间,或者自定义一些其他增强的功能.
ThreadPoolExecutor 线程池提供了两个方法: protected void afterExecute(Runnable r, Throwable t) protected void beforeExecute(Thread t, Runnable r)
在线程池执行某个任务前会调用 beforeExecute()方法,在任务结束 后(任务异常退出)会执行 afterExecute()方法
查看 ThreadPoolExecutor 源码,在该类中定义了一个内部类 Worker, ThreadPoolExecutor 线程池中的工作线程就是 Worker 类的实例, Worker 实例在执行时会调用 beforeExecute()与 afterExecute()方法
-
优化线程池的大小:
线程池大小对系统性能是有一定影响的,过大或者过小都会无法 发挥最优的系统性能, 线程池大小不需要非常精确,只要避免极大或 者极小的情况即可, 一般来说,线程池大小需要考虑 CPU 数量,内存大 小等因素. 在书中给出一个估算线程池
大小的公式: 线程池大小 = CPU 的数量 * 目标 CPU 的使用率*( 1 + 等待时间 与计算时间的比) -
线程池死锁:
如果在线程池中执行的 任务 A 在执行过程中又向线程池提交了 任务 B, 任务 B 添加到了线程池的等待队列中, 如果任务 A 的结束需 要等待任务 B 的执行结果. 就有可能会出现这种情况: 线程池中所有 的工作线程都处于等待任务处理结果,而这些任务在阻塞队列中等待 执行, 线程池中没有可以对阻塞队列中的任务进行处理的线程,这种 等待会一直持续下去,从而造成死锁.
适合给线程池提交相互独立的任务,而不是彼此依赖的任务. 对于 彼此依赖的任务,可以考虑分别提交给不同的线程池来执行. -
线程池异常处理:
在使用 ThreadPoolExecutor 进行 submit 提交任务时,有的任务抛出 了异常,但是线程池并没有进行提示,即线程池把任务中的异常给吃掉 了,可以把 submit 提交改为 execute 执行,也可以对 ThreadPoolExecutor 线程池进行扩展,重写 submit 方法。 -
ForkJoinPool 线程池
“分而治之”是一个有效的处理大数据的方法,著名的 MapReduce 就是采用这种分而治之的思路. 简单点说,如果要处理的 1000 个数据, 但是我们不具备处理1000个数据的能力,可以只处理10个数据, 可以 把这 1000 个数据分阶段处理 100 次,每次处理 10 个,把 100 次的处理 结果进行合成,形成最后这 1000 个数据的处理结果. 把一个大任务调用 fork()方法分解为若干小的任务,把小任务的处 理结果进行 join()合并为大任务的结果
![]()
系统对 ForkJoinPool 线程池进行了优化,提交的任务数量与线程的 数量不一定是一对一关系.在多数情况下,一个物理线程实际上需要处 理多个逻辑任务.
![]()
ForkJoinPool 线程池中最常用 的方法是:
ForkJoinTask submit(ForkJoinTask task) 向线程池提交 一个 ForkJoinTask 任务. ForkJoinTask 任务支持 fork()分解与 join()等待的 任 务 . ForkJoinTask 有 两 个 重 要 的 子 类 :RecursiveAction 和 RecursiveTask ,它们的区别在于 RecursiveAction 任务没有返回值, RecursiveTask 任务可以带有返回值.




浙公网安备 33010602011771号