JDK源码走读之深入理解线程池(ThreadPoolExecutor)

来源:互联网 发布:attr 属性 boolean js 编辑:程序博客网 时间:2024/05/21 06:14

Java线程池提供了一个框架来统一管理线程,通过把提交任务和执行任务解耦,使开发者无需关心线程的运行状态,只要把任务提交给线程池既可。使用线程池有以下好处:
1. 减少在创建和销毁线程上所花的时间以及系统资源的开销
2. 避免创建过多的线程导致系统资源过度消耗、系统执行效率低下

线程池可以通过工厂类Executors创建,大致分为以下几种:
1. newFixedThreadPool(int nThreads) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
2. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
3. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
4. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行

这里写图片描述

下面深入源码来分析一下线程池的实现

Executor框架

这里写图片描述
Executor接口是整个框架的基础,下面所有类和接口都是对Executor的扩展。ThreadPoolExecutor和ScheduledThreadPoolExecutor是线程池的俩个核心类,前者是普通的线程池,后者继承前者扩展了任务定时、延迟执行等功能。
Executors则是一个工厂类,屏蔽了线程池的创建过程,通过调用不同的方法传入合适的参数类实例化不同类型的线程池。
## ThreadPoolExecutor 定义##
这里写图片描述
ThreadPoolExecutor主要由4部分构成

  1. 线程池的状态,运行、终止、当前线程数等等,用于对线程池监控
  2. 工作队列,用来保存提交的任务,队列可以有不同类型的实现
  3. 工作(消费)线程,用来执行工作队列里的任务
  4. 提交任务方法,以及一些其他方法

任务处理流程

这里写图片描述
线程池的核心是生产者消费者模型,提交一个新的任务主要流程如图所示:

  1. 线程池里的线程是否小于核心线程数,如果是则创建一个新的线程执行任务,如果不是执行2
  2. 工作队列是否已满,如果没有满则把任务放入队列中,如果满了执行3
  3. 判断线程池是否已满(到达线程个数上限),如果没满则创建新的线程执行任务,如果满了执行4
  4. 按照定义的不同策略处理无法执行的任务,如直接抛弃任务、抛出异常等等

Worker

这里写图片描述
Worker为ThreadPoolExecutor的静态内部类,实现了runnable接口并集成了AbstractQueuedSynchonrizer类,这个类有两个主要的成员变量。

  1. firstTask用来保存runnable任务,提交新任务到线程池,当核心线程池不满时会创建worker并且把提交的任务赋给这个worker,确保当前任务优先执行。
  2. thread对象,每创建一个worker都会对应创建一个thread对象,并且把worker自身作为task传入thread,这样执行thread.start就可以调用到worker的run方法

    这里写图片描述
    Worker的run方法调用外部类的runWorker方法

    1. 循环调用getTask()方法获取队列中的任务执行,执行前加锁执行结束释放锁
    2. 执行任务前后根据业务场景自定义beforeExecute和afterExecute方法

这里写图片描述
getTask方法会不断的循环,从队列中取任务

  1. workQueue.take:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;
  2. workQueue.poll:如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;

提交任务

这里写图片描述
调用execute(Runnable command)方法把任务提交到线程池

  1. 线程池里的线程小于核心线程数,创建新的线程
  2. 队列未满(往队列里添加任务成功)
  3. 当核心线程已满,队列已满的情况下尝试创建非核心线程

    这里写图片描述

  4. 首选判断线程池的状态,如果线程池的状态值大于SHUTDOWN,则不处理提交任务,直接返回

  5. 如果线程数量不满足要求,则直接返回不做处理。添加核心线程要求当前线程数小于核心线程数;添加非核心线程要求当前线程数小于线程池数量上限;且线程最大不能超过最大容量
  6. 利用CAS尝试修改线程个数使其+1,如果成功则直接跳出循环,开始创建线程

    这里写图片描述

  7. 把提交的任务最为task构造worker
  8. 把任务添加到工作队列,在添加之前获取锁添加完成释放,防止并发造成数据异常
  9. 最后执行t.start()启动worker,worker会通过run方法调用外部类的runWorker方法不断的从任务队列中消费任务

创建线程池

这里写图片描述

  1. corePoolSize 核心线程数线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
  2. 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
  3. 线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;
  4. keepAliveTime的单位;
  5. 用来保存执行任务的阻塞队列,该队列泛型限制只接收Runnable类型的元素。根据传入的队列性质不同,线程池的特性也不同。

Executors是对构造方法的封装,通过传入不同的参数,使线程池具有不同的特性,不再详细描述。

总结

  1. 每个worker对应一个thread,线程池通过控制worker的数量来控制并发线程数
  2. 线程池实质是一个生产者消费者模型,它有一个任务队列和一个消费线程集合组成。
  3. 任务通常会被提交到队列中,由消费线程执行。
  4. 运行的worker线程会通过run方法调用外部类的runWorker方法不断消费任务。
阅读全文
0 0
原创粉丝点击