线程池

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 workQueue, ------ 任务队列,把任务提交到该任务队列中等待执行
ThreadFactory threadFactory, ------ 线程工厂,用于创建线程
RejectedExecutionHandler handler) --- 拒绝策略,当任务太多来不及处理时,如何拒绝
............................................................................................................................................................
说明:

workQueue 工 作 队 列 是 指 提 交 未 执 行 的 任 务 队 列 , 它 是 BlockingQueue 接口的对象,仅用于存储 Runnable 任务.根据队列功 能分类,在 ThreadPoolExecutor 构造方法中可以使用以下几种阻塞 队列:

1)) 直接提交队列,由 SynchronousQueue 对象提供,该队列没有 容量,提交给线程池的任务不会被真实的保存,总是将新的任务提 交给线程执行,如果没有空闲线程,则尝试创建新的线程,如果线程 数量已经达到 maxinumPoolSize 规定的最大值则执行拒绝策略.

  1. )有 界 任 务 队 列 , 由 ArrayBlockingQueue 实 现 , 在 创 建 ArrayBlockingQueue 对象时,可以指定一个容量. 当有任务需要执 行时,如果线程池中线程数小于 corePoolSize 核心线程数则创建新 的线程;如果大于 corePoolSize 核心线程数则加入等待队列.如果队 列已满则无法加入,在线程数小于 maxinumPoolSize 指定的最大线 程 数 前 提 下 会 创 建 新 的 线 程 来 执 行 , 如 果 线 程 数 大 于 maxinumPoolSize 最大线程数则执行拒绝策略

3 ) 无界任务队列,由 LinkedBlockingQueue 对象实现,与有界队 列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的 情况. 当有新的任务时,在系统线程数小于 corePoolSize 核心线程 数则创建新的线程来执行任务;当线程池中线程数量大于corePoolSize 核心线程数则把任务加入阻塞队列

  1. )优先任务队列是通过 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 getQueue() 返回阻塞队列

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 任务可以带有返回值.

posted @ 2021-08-26 22:14  x77  阅读(79)  评论(0)    收藏  举报