为什么要使用线程池?
线程的创建和销毁的代价是很高的。
使用线程池将线程复用,比如池中有十个线程,十个线程不退出,没有任务是等待状态,任务开始则执行,线程不会销毁,可以一直执行任务。
使用线程池只需将任务放入到线程池中,线程池会自动创建线程去执行任务,不需要自己创建。
一个线程池包含以下几个部分:
- 线程池管理器(ThreadPool):用于创建并管理线程池,包含创建线程池,销毁线程池,添加新任务
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。
- 任务接口(Task):每个任务必须实现的接口
- 任务队列(taskQuene):用于存放没有处理的任务,提供一种缓存机制
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池ThreadPoolExecutor是线程池的真正实现
ThreadPoolExecutor构造函数参数:
corePoolSize:池中所保存核心线程数,包含空闲线程。默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime
限制。除非将allowCoreThreadTimeOut
设置为true
。
maximumPoolSize:池中所允许的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime:当线程数大于核心时,多余的空闲线程的存活时间,多长时间内会被销毁。
TimeUnit:keepAliveTime参数的时间单位。如TimeUnit.SECONDS
。当将allowCoreThreadTimeOut
设置
为true
时对corePoolSize生效。
workQueue:任务队列,被提交但尚未被执行的任务。
threadFactory:线程工厂,用于创建线程,一般用默认的
handler:任务拒绝策略,当任务太多来不及处理,如何拒绝任务。
- 直接抛异常,这是默认策略
- 直接丢弃任务
- 丢弃最前面的任务,并执行该任务
- 用调用者所在的线程处理该任务
线程池的执行逻辑
- 如果当前worker数量小于corePoolSize,则新建一个worker并把当前任务分配给该worker线程,成功则返回。
- 如果第一步失败,则尝试将任务放入阻塞队列,如果成功则返回
- 如果第二部失败,则判断当前worker数小于maximumPoolSize,则新建一个worker并把当前任务分配给该worker线程,成功则返回。
- 如果第三步失败,则调用拒绝策略处理该任务。
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的,不过,就算队列里面有任务,线程池也不会马上执行它们。
2、当调用execute()方法添加一个任务时,线程池会做如下判断:
a、如果正在运行的线程数小于核心线程数,那么马上创建线程执行这个任务
b、如果正在运行的线程数大于或等于核心线程数,那么将这个任务放入队列
c、如果这时候队列满了,而且正在运行的线程数小于最大线程数,那么还是要创建非核心线程立刻运行这个任务
d、如果队列满了,而且正在运行的线程数大于等于最大线程数,那么线程池会抛出异常RejectExecutionException。
3、当一个线程完成任务时,他会从队列中取下一个任务来执行
4、当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于核心线程数,那么这个线程会被停掉。所以线程池所有任务完成后,它最终会收缩到核心线程数大小。
创建线程池的方式
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程工作,如果这个唯一线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有的任务执行顺序按照任务提交顺序执行
- newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到达到池中的最大大小。线程池的大小一旦达到最大值,就会一直保持不变。
- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲线程(60s不执行任务的),当任务数增加时,线程池又可以智能的添加新线程处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduleThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性的执行任务的需求。
关闭线程池
1、void shutdown();
这个方法是一种安全关闭线程池的方法,调用这个方法后,线程池会根据拒绝策略来拒绝新提供的任务,然后线程池会把正在执行的任务,和在队列中等待任务都执行完后才会彻底关闭。
2、List
调用这个方法后,线程池会立即关闭,从源码上分析,会发现调用了方法之后,会给所有线程池中的线程发送interrupt中断信号,来尝试中断这些任务的执行,不过也需要线程自身具有响应中断信号的能力,同时这个方法返回值,该集合就是那些任务队列中正在等待被执行的任务集合。